packages feed

jvm-batching-0.2.0: src/main/haskell/Language/Java/Batching.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StaticPointers #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

-- | This module provides composable batched marshalling.
--
-- It provides reify and reflect instances for vectors, which marshal
-- values in batches, which is more efficient than marshalling values
-- one at a time.
--
-- > instance (Interpretation a, BatchReify a) => Reify (V.Vector a) where
-- > ...
--
-- > instance (Interpretation a, BatchReflect a) => Reflect (V.Vector a) where
-- > ...
--
-- === Batching
--
-- Calls to Java methods via JNI are slow in general. Marshalling an array of
-- primitive values can be as slow as marshalling a single value.
--
-- Because of this, reifying an iterator or a container is best done by
-- accumulating multiple elements on the java side before passing them to the
-- Haskell side. And conversely, when reflecting an iterator or container,
-- multiple Haskell values are put together before marshalling to the Java
-- side.
--
-- Some Haskell values can be batched trivially into arrays of primitive values.
-- 'Int32' can be batched in a java @int[]@, 'Double' can be batched in a java
-- @double[]@, etc. However, other types like @Tuple2 Int32 Double@ would
-- require more primitive arrays. Values of type @Tuple2 Int32 Double@ are
-- batched in a pair of java arrays of type @int[]@ and @double[]@.
--
-- > data Tuple2 a b = Tuple2 a b
--
-- More generally, the design aims to provide composable batchers. If one knows
-- how to batch types @a@ and @b@, one can also batch @Tuple2 a b@, @[a]@,
-- @Vector a@, etc.
--
-- A reference to a batch of values in Java has the type @J (Batch a)@, where
-- @a@ is the Haskell type of the elements in the batch. e.g.
--
-- > type instance Batch Int32 = 'Array ('Prim "int")
-- > type instance Batch Double = 'Array ('Prim "double")
-- > type instance Batch (Tuple2 a b) =
-- >                 'Class "scala.Tuple2" <> '[Batch a, Batch b]
--
-- When defining batching for a new type, one needs to tell how batches are
-- represented in Java by adding a type instance to the type family @Batch@.
-- In addition, procedures for adding and extracting values from the batch
-- need to be specified on both the Haskell and the Java side.
--
-- On the Java side, batches are built using the interface
-- @io.tweag.jvm.batching.BatchWriter@. On the Haskell side, these
-- batches are read using @reifyBatch@.
--
-- > class ( ... ) => BatchReify a where
-- >   newBatchWriter
-- >     :: proxy a
-- >     -> IO (J ('Iface "io.tweag.jvm.batching.BatchWriter"
-- >                  <> [Interp a, Batch a]
-- >              )
-- >           )
-- >   reifyBatch :: J (Batch a) -> Int32 -> IO (V.Vector a)
--
-- @newBatchWriter@ produces a java object implementing the @BatchWriter@
-- interface, and @reifyBatch@ allows to read a batch created in this fashion.
--
-- Conversely, batches can be read on the Java side using the interface
-- @io.tweag.jvm.batching.BatchReader@. And on the Haskell side, these
-- batches can be created with @reflectBatch@.
--
-- > class ( ... ) => BatchReflect a where
-- >  newBatchReader
-- >    :: proxy a
-- >    -> IO (J ('Iface "io.tweag.jvm.batching.BatchReader"
-- >                 <> [Batch a, Interp a]
-- >              )
-- >          )
-- >  reflectBatch :: V.Vector a -> IO (J (Batch a))
--
-- @newBatchReader@ produces a java object implementing the @BatchReader@
-- interface, and @reflectBatch@ allows to create these batches from vectors of
-- Haskell values.
--
-- The methods of @BatchReify@ and @BatchReflect@ offer default
-- implementations which marshal elements in the batch one at a time. Taking
-- advantage of batching requires defining the methods explicitly. The default
-- implementations are useful for cases where speed is not important, for
-- instance when the iterators to reflect or reify contain a single element or
-- just very few.
--
-- 'Vector's and 'ByteString's are batched with the follow scheme.
--
-- > type instance Batch BS.ByteString
-- >   = 'Class "io.tweag.jvm.batching.Tuple2" <>
-- >        '[ 'Array ('Prim "byte")
-- >         , 'Array ('Prim "int")
-- >         ]
--
-- We use two arrays. One of the arrays contains the result of appending all of
-- the 'ByteString's in the batch. The other array contains the offset of each
-- vector in the resulting array. See 'ArrayBatch'.
--
-- === null values in batches
--
-- Suppose we have an array of pairs in Java:
--
-- > new Tuple2<Integer,Integer>[] { null, new Tuple2(1, 2), new Tuple2(3, 4) }
--
-- In Haskell we can represent it using the 'Nullable' type
--
-- > [ Null, NotNull (1, 2), NotNull (3, 4) ]
--
-- However, we can't use the batch type we defined above for @Tuple2@,
-- because there is no way to store a null value in a pair of primitive arrays.
--
-- For this reason we define a special batch type for values that can be null.
-- We place a boolean array together with the regular batch of values.
--
-- > type Batch (Nullable a) =
-- >   'Class "io.tweag.jvm.batching.Tuple2" <>
-- >     '[ 'Array ('Prim "boolean")
-- >      , ArrayBatch (Batch a)
-- >      ]
--
-- The positions in the batch for type @a@ are only meaningful if the boolean
-- array holds @True@ in the corresponding positions, the remaining positions
-- are regarded to hold null values.
--

module Language.Java.Batching
  ( Batchable(..)
  , BatchReify(..)
  , BatchReflect(..)
    -- * Array batching
  , ArrayBatch
  ) where

import Control.Distributed.Closure.TH
import Control.Exception (bracket)
import Control.Monad (forM_, foldM)
import qualified Data.ByteString             as BS
import qualified Data.ByteString.Unsafe      as BS
import Data.Int
import Data.Singletons (SingI, Proxy(..))
import qualified Data.Text as Text
import qualified Data.Text.Foreign as Text
import qualified Data.Vector as V
import qualified Data.Vector.Mutable as VM
import qualified Data.Vector.Storable as VS
import Data.Word
import Foreign.C.Types (CChar)
import Foreign.ForeignPtr (newForeignPtr_, withForeignPtr)
import Foreign.JNI
import Foreign.Ptr
import Foreign.Storable
import Language.Java
import Language.Java.Inline

imports "io.tweag.jvm.batching.*"

-- | A class of types whose values can be marshaled in batches.
class (Interpretation a, SingI (Batch a)) => Batchable (a :: k) where
  -- | The type of java batches for reifying and reflecting values of type @a@.
  type family Batch a :: JType

-- | A class for batching reification of values.
--
-- It has a method to create a batcher that creates batches in Java, and
-- another method that refies a batch into a vector of haskell values.
--
-- The type of the batch used to appear as a class parameter but we run into
-- https://ghc.haskell.org/trac/ghc/ticket/13582
--
class Batchable a => BatchReify a where
  -- | Produces a batcher that aggregates elements of type @ty@ (such as @int@)
  -- and produces collections of type @Batch a@ (such as @int[]@).
  newBatchWriter
    :: proxy a
    -> IO (J ('Iface "io.tweag.jvm.batching.BatchWriter"
                 <> [Interp a, Batch a]
             )
          )

  -- The default implementation makes calls to the JVM for each element in the
  -- batch.
  default newBatchWriter
    :: (Batch a ~ 'Array (Interp a))
    => proxy a
    -> IO (J ('Iface "io.tweag.jvm.batching.BatchWriter"
                 <> [Interp a, Batch a]
             )
          )
  newBatchWriter _ = generic <$> [java| new BatchWriters.ObjectBatchWriter() |]

  -- | Reifies the values in a batch of type @Batch a@.
  --
  -- Gets the batch, the amount of elements it contains and a predicate that
  -- indicates which positions of the batch to reify. The value at position i
  -- in the returned vector holds bottom if @p i@ is False.
  --
  -- The predicate is used by instances of BatchReify to control which
  -- positions are to be reified in the batches of the components of a
  -- composite type whose values are allowed to be null.
  reifyBatch :: J (Batch a) -> Int32 -> (Int -> Bool) -> IO (V.Vector a)

  -- The default implementation makes calls to the JVM for each element in the
  -- batch.
  default reifyBatch
    :: (Reify a, Batch a ~ 'Array (Interp a))
    => J (Batch a) -> Int32 -> (Int -> Bool) -> IO (V.Vector a)
  reifyBatch jxs size p =
    V.generateM (fromIntegral size) $ \i ->
      if p i then
        getObjectArrayElement jxs (fromIntegral i) >>= reify . unsafeCast
      else
        return $ error "default reifyBatch: reification skipped."

-- | Helper for reifying batches of primitive types
reifyPrimitiveBatch
  :: Storable a
  => (J ('Array ty) -> IO (Ptr a))
  -> (J ('Array ty) -> Ptr a -> IO ())
  -> J ('Array ty)
  -> Int32
  -> (Int -> Bool)
  -> IO (V.Vector a)
reifyPrimitiveBatch getArrayElements releaseArrayElements jxs size p = do
    bracket (getArrayElements jxs) (releaseArrayElements jxs) $ \arr ->
      V.generateM (fromIntegral size) $ \i ->
        if p i then peekElemOff arr i
        else return $ error "default reifyBatch: reification skipped."

-- | Batches of arrays of variable length
--
-- The first component is an array or batch B containing the elements
-- of all the arrays in the batch. The second component is an array of
-- offsets F. The ith position in the offset array is the first position
-- in B after the ith array of the batch.
--
-- Thus, the first array of the batch can be found in B between the
-- indices 0 and F[0], the second array of the batch is between the
-- indices F[0] and F[1], and so on.
--
type ArrayBatch ty =
    'Class "io.tweag.jvm.batching.Tuple2" <>
       '[ ty
        , 'Array ('Prim "int")
        ]

-- | Helper for reifying batches of vectors
--
-- Arrays are batched with two arrays. One of the arrays contains the result
-- of appending all of the vectors in the batch. The other array contains the
-- offset of each vector in the resulting array.
--
reifyArrayBatch
  :: forall a b ty.
     (Int32 -> J ty -> IO a) -- ^ reify the array/batch of values
                             -- (takes the amount of elements in the array)
  -> (Int -> Int -> a -> IO b) -- ^ slice at a given offset of given length of some array a
  -> J (ArrayBatch ty)
  -> Int32
  -> (Int -> Bool)
  -> IO (V.Vector b)
reifyArrayBatch reifyB slice batch0 batchSize p = do
    let batch = unsafeUngeneric batch0
    arrayEnds <- reifyArrayOffsets batch
    arrayValues <- reifyArrayValues arrayEnds batch
    reifySlices arrayEnds arrayValues
  where
    fromObject :: JObject -> J x
    fromObject = unsafeCast

    reifyArrayOffsets
      :: J ('Class "io.tweag.jvm.batching.Tuple2") -> IO (VS.Vector Int32)
    reifyArrayOffsets batch = [java| (int[])$batch._2 |] >>= reify

    reifyArrayValues arrayEnds batch = do
      let count = if VS.null arrayEnds then 0 else VS.last arrayEnds
      [java| $batch._1 |] >>= reifyB count . fromObject

    reifySlices arrayEnds arrayValues = do
      result <- VM.new (fromIntegral batchSize)
      _ <- foldM
           (writeSliceToVector result arrayEnds arrayValues)
           0
           [0 .. fromIntegral batchSize - 1]
      V.unsafeFreeze result

    writeSliceToVector
      :: VM.IOVector b   -- ^ output vector to write to
      -> VS.Vector Int32 -- ^ ends[i] holds the offset of the (i+1)th slice
      -> a               -- ^ input vector to read slices from
      -> Int32           -- ^ offset of the slice to read from the input vector
      -> Int             -- ^ index of the position to write in the output vector
      -> IO Int32        -- ^ offset of the next slice to read
    writeSliceToVector output arrayEnds arrayValues offset i = do
        -- Account for the posibility of @arrayEnds ! i@ to be 0
        -- in case the position in the batch has been left uninitialized.
        let nextOffset = max (arrayEnds VS.! i) offset
        if p i then do
          slice (fromIntegral offset)
                (fromIntegral $ nextOffset - offset) arrayValues
            >>= VM.unsafeWrite output i
          return nextOffset
        else do
          VM.unsafeWrite output i $
            error "reifyPrimitiveArrayBatch: reification skipped."
          return offset

-- | A class for batching reflection of values.
--
-- It has a method to create a batch reader that reads batches in Java, and
-- another method that reflects a vector of haskell values into a batch.
--
-- We considered having the type of the batch appear as a class parameter but
-- we run into https://ghc.haskell.org/trac/ghc/ticket/13582
--
class Batchable a => BatchReflect a where
  -- | Produces a batch reader that receives collections of type @ty1@
  -- (such as @int[]@) and produces values of type @ty2@ (such as @int@).
  newBatchReader
    :: proxy a
    -> IO (J ('Iface "io.tweag.jvm.batching.BatchReader"
                 <> [Batch a, Interp a]
             )
          )

  -- The default implementation makes calls to the JVM for each element in the
  -- batch.
  default newBatchReader
    :: (Batch a ~ 'Array (Interp a))
    => proxy a
    -> IO (J ('Iface "io.tweag.jvm.batching.BatchReader"
                       <> [Batch a, Interp a]
             )
          )
  newBatchReader _ =
      generic <$> [java| new BatchReaders.ObjectBatchReader() |]

  -- | Reflects the values in a vector to a batch of type @ty@.
  reflectBatch :: V.Vector a -> IO (J (Batch a))
  -- The default implementation makes calls to the JVM for each element in the
  -- batch.
  default reflectBatch
    :: (Reflect a, Batch a ~ 'Array (Interp a))
    => V.Vector a -> IO (J (Batch a))
  reflectBatch v = do
      jxs <- newArray $ fromIntegral (V.length v)
      forM_ [0 .. V.length v - 1] $ \i ->
        withLocalRef (reflect (v V.! i))
                     (setObjectArrayElement jxs (fromIntegral i))
      return jxs

-- | Helper for reflecting batches of primitive types
reflectPrimitiveBatch
  :: forall a ty. (Storable a, IsPrimitiveType ty)
  => (J ('Array ty) -> Int32 -> Int32 -> Ptr a -> IO ())
  -> V.Vector a -> IO (J ('Array ty))
reflectPrimitiveBatch setArrayRegion v = do
    let (fptr, offset, len) = VS.unsafeToForeignPtr (V.convert v)
    withForeignPtr fptr $ \ptr -> do
      jxs <- newArray (fromIntegral len)
      let aOffset = offset * sizeOf (undefined :: a)
      setArrayRegion jxs 0 (fromIntegral len) (plusPtr ptr aOffset)
      return jxs

-- | Helper for reflecting batches of vectors
--
-- The vector type is a, and vectors are manipulated exclusively with the
-- polymorphic functions given as arguments.
--
-- Vectors are batched with two arrays. One of the arrays contains the result
-- of appending all of the vectors in the batch. The other array contains the
-- offset of each vector in the resulting array.
--
reflectArrayBatch
  :: forall a b ty.
     (b -> IO (J ty))
  -> (a -> Int) -- ^ get length
  -> ([a] -> IO b) -- ^ concat
  -> V.Vector a
  -> IO (J (ArrayBatch ty))
reflectArrayBatch reflectB getLength concatenate vecs = do
    let ends = V.convert $ V.postscanl' (+) 0 $
                V.map (fromIntegral . getLength) vecs
                  :: VS.Vector Int32
    bigvec <- concatenate $ V.toList vecs
    jvec <- reflectB bigvec
    jends <- reflect ends
    generic <$> Language.Java.new (upcast jvec) (upcast jends)

withStatic [d|
  instance Batchable Bool where
    type Batch Bool = 'Array ('Prim "boolean")

  instance BatchReify Bool where
    newBatchWriter _ = [java| new BatchWriters.BooleanBatchWriter() |]
    reifyBatch jxs size p = do
        let toBool w = if w == 0 then False else True
        bracket (getBooleanArrayElements jxs)
                (releaseBooleanArrayElements jxs) $
          \arr -> V.generateM (fromIntegral size) $ \i ->
            if p i then
              toBool <$> peekElemOff arr i
            else
              return $ error "reifyBatch Bool: reification skipped."

  instance Batchable CChar where
    type Batch CChar = 'Array ('Prim "byte")

  instance BatchReify CChar where
    newBatchWriter _ = [java| new BatchWriters.ByteBatchWriter() |]
    reifyBatch =
      reifyPrimitiveBatch getByteArrayElements releaseByteArrayElements

  instance Batchable Word16 where
    type Batch Word16 = 'Array ('Prim "char")

  instance BatchReify Word16 where
    newBatchWriter _ = [java| new BatchWriters.CharacterBatchWriter() |]
    reifyBatch =
      reifyPrimitiveBatch getCharArrayElements releaseCharArrayElements

  instance Batchable Int16 where
    type Batch Int16 = 'Array ('Prim "short")

  instance BatchReify Int16 where
    newBatchWriter _ = [java| new BatchWriters.ShortBatchWriter() |]
    reifyBatch =
      reifyPrimitiveBatch getShortArrayElements releaseShortArrayElements

  instance Batchable Int32 where
    type Batch Int32 = 'Array ('Prim "int")

  instance BatchReify Int32 where
    newBatchWriter _ = [java| new BatchWriters.IntegerBatchWriter() |]
    reifyBatch =
      reifyPrimitiveBatch getIntArrayElements releaseIntArrayElements

  instance Batchable Int64 where
    type Batch Int64 = 'Array ('Prim "long")

  instance BatchReify Int64 where
    newBatchWriter _ = [java| new BatchWriters.LongBatchWriter() |]
    reifyBatch =
      reifyPrimitiveBatch getLongArrayElements releaseLongArrayElements

  instance Batchable Float where
    type Batch Float = 'Array ('Prim "float")

  instance BatchReify Float where
    newBatchWriter _ = [java| new BatchWriters.FloatBatchWriter() |]
    reifyBatch =
      reifyPrimitiveBatch getFloatArrayElements releaseFloatArrayElements

  instance Batchable Double where
    type Batch Double = 'Array ('Prim "double")

  instance BatchReify Double where
    newBatchWriter _ = [java| new BatchWriters.DoubleBatchWriter() |]
    reifyBatch =
      reifyPrimitiveBatch getDoubleArrayElements releaseDoubleArrayElements

  instance BatchReflect Bool where
    newBatchReader _ = [java| new BatchReaders.BooleanBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setBooleanArrayRegion
                 . V.map (\w -> if w then 1 else 0)

  instance BatchReflect CChar where
    newBatchReader _ = [java| new BatchReaders.ByteBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setByteArrayRegion

  instance BatchReflect Word16 where
    newBatchReader _ = [java| new BatchReaders.CharacterBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setCharArrayRegion

  instance BatchReflect Int16 where
    newBatchReader _ = [java| new BatchReaders.ShortBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setShortArrayRegion

  instance BatchReflect Int32 where
    newBatchReader _ = [java| new BatchReaders.IntegerBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setIntArrayRegion

  instance BatchReflect Int64 where
    newBatchReader _ = [java| new BatchReaders.LongBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setLongArrayRegion

  instance BatchReflect Float where
    newBatchReader _ = [java| new BatchReaders.FloatBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setFloatArrayRegion

  instance BatchReflect Double where
    newBatchReader _ = [java| new BatchReaders.DoubleBatchReader() |]
    reflectBatch = reflectPrimitiveBatch setDoubleArrayRegion

#if ! (__GLASGOW_HASKELL__ == 800 && __GLASGOW_HASKELL_PATCHLEVEL1__ == 1)
  instance Batchable BS.ByteString where
    type Batch BS.ByteString
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "byte")
          , 'Array ('Prim "int")
          ]

  instance Batchable (VS.Vector Word16) where
    type Batch (VS.Vector Word16)
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "char")
          , 'Array ('Prim "int")
          ]

  instance Batchable (VS.Vector Int16) where
    type Batch (VS.Vector Int16)
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "short")
          , 'Array ('Prim "int")
          ]

  instance Batchable (VS.Vector Int32) where
    type Batch (VS.Vector Int32)
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "int")
          , 'Array ('Prim "int")
          ]

  instance Batchable (VS.Vector Int64) where
    type Batch (VS.Vector Int64)
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "long")
          , 'Array ('Prim "int")
          ]

  instance Batchable (VS.Vector Float) where
    type Batch (VS.Vector Float)
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "float")
          , 'Array ('Prim "int")
          ]

  instance Batchable (VS.Vector Double) where
    type Batch (VS.Vector Double)
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "double")
          , 'Array ('Prim "int")
          ]

  instance Batchable Text.Text where
    type Batch Text.Text
      = 'Class "io.tweag.jvm.batching.Tuple2" <>
         '[ 'Array ('Prim "char")
          , 'Array ('Prim "int")
          ]

  instance BatchReify BS.ByteString where
    newBatchWriter _ = [java| new BatchWriters.ByteArrayBatchWriter() |]
    reifyBatch = reifyArrayBatch (const reify) bsUnsafeSlice
      where
        bsUnsafeSlice :: Int -> Int -> BS.ByteString -> IO BS.ByteString
        bsUnsafeSlice offset sz = return . BS.unsafeTake sz . BS.unsafeDrop offset

  instance BatchReify (VS.Vector Word16) where
    newBatchWriter _ = [java| new BatchWriters.CharArrayBatchWriter() |]
    reifyBatch =
        reifyArrayBatch (const reify) (fmap (fmap return) . VS.unsafeSlice)

  instance BatchReify (VS.Vector Int16) where
    newBatchWriter _ = [java| new BatchWriters.ShortArrayBatchWriter() |]
    reifyBatch =
        reifyArrayBatch (const reify) (fmap (fmap return) . VS.unsafeSlice)

  instance BatchReify (VS.Vector Int32) where
    newBatchWriter _ = [java| new BatchWriters.IntArrayBatchWriter() |]
    reifyBatch =
        reifyArrayBatch (const reify) (fmap (fmap return) . VS.unsafeSlice)

  instance BatchReify (VS.Vector Int64) where
    newBatchWriter _ = [java| new BatchWriters.LongArrayBatchWriter() |]
    reifyBatch =
        reifyArrayBatch (const reify) (fmap (fmap return) . VS.unsafeSlice)

  instance BatchReify (VS.Vector Float) where
    newBatchWriter _ = [java| new BatchWriters.FloatArrayBatchWriter() |]
    reifyBatch =
        reifyArrayBatch (const reify) (fmap (fmap return) . VS.unsafeSlice)

  instance BatchReify (VS.Vector Double) where
    newBatchWriter _ = [java| new BatchWriters.DoubleArrayBatchWriter() |]
    reifyBatch =
        reifyArrayBatch (const reify) (fmap (fmap return) . VS.unsafeSlice)

  instance BatchReify Text.Text where
    newBatchWriter _ = [java| new BatchWriters.StringArrayBatchWriter() |]
    reifyBatch = reifyArrayBatch (const reify) $ \o n vs ->
                  (VS.unsafeWith (VS.unsafeSlice o n vs) $ \ptr ->
                      Text.fromPtr ptr (fromIntegral n)
                  )

  instance BatchReflect BS.ByteString where
    newBatchReader _ = [java| new BatchReaders.ByteArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect BS.length (return . BS.concat)

  instance BatchReflect (VS.Vector Word16) where
    newBatchReader _ = [java| new BatchReaders.CharArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect VS.length (return . VS.concat)

  instance BatchReflect (VS.Vector Int16) where
    newBatchReader _ = [java| new BatchReaders.ShortArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect VS.length (return . VS.concat)

  instance BatchReflect (VS.Vector Int32) where
    newBatchReader _ = [java| new BatchReaders.IntArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect VS.length (return . VS.concat)

  instance BatchReflect (VS.Vector Int64) where
    newBatchReader _ = [java| new BatchReaders.LongArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect VS.length (return . VS.concat)

  instance BatchReflect (VS.Vector Float) where
    newBatchReader _ = [java| new BatchReaders.FloatArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect VS.length (return . VS.concat)

  instance BatchReflect (VS.Vector Double) where
    newBatchReader _ = [java| new BatchReaders.DoubleArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect VS.length (return . VS.concat)

  instance BatchReflect Text.Text where
    newBatchReader _ = [java| new BatchReaders.StringArrayBatchReader() |]
    reflectBatch = reflectArrayBatch reflect Text.length $ \ts ->
                     Text.useAsPtr (Text.concat ts) $ \ptr len ->
                       (`VS.unsafeFromForeignPtr0` fromIntegral len)
                         <$> newForeignPtr_ ptr
#endif

  instance Interpretation a => Interpretation (V.Vector a) where
    type Interp (V.Vector a) = 'Array (Interp a)

  -- TODO: Fix GHC so it doesn't complain that variables used exclusively in
  -- quasiquotes are unused. Thus we can stop prepending variable names with
  -- '_'.

  instance (Interpretation a, BatchReify a)
           => Reify (V.Vector a) where
    reify jv =
        withLocalRef (unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a))
        $ \_batcher -> do
          n <- getArrayLength jv
          let _jvo = arrayUpcast jv
          withLocalRef [java| {
            $_batcher.start($n);
            for(int i=0;i<$_jvo.length;i++)
               $_batcher.set(i, $_jvo[i]);
            return $_batcher.getBatch();
            } |]
            $ \batch ->
              reifyBatch (fromObject batch) n (const True)
      where
        fromObject :: JObject -> J x
        fromObject = unsafeCast

  instance (Interpretation a, BatchReflect a)
           => Reflect (V.Vector a) where
    reflect v =
        withLocalRef (upcast <$> reflectBatch v) $ \_batch ->
        withLocalRef (unsafeUngeneric <$> newBatchReader (Proxy :: Proxy a))
        $ \_batchReader -> do
          jv <- [java| {
            $_batchReader.setBatch($_batch);
            return $_batchReader.getSize();
            } |] >>= newArray :: IO (J ('Array (Interp a)))
          let _jvo = arrayUpcast jv
          () <- [java| {
            for(int i=0;i<$_jvo.length;i++)
              $_jvo[i] = $_batchReader.get(i);
            } |]
          return jv

  instance Batchable a => Batchable (V.Vector a) where
    type Batch (V.Vector a) = ArrayBatch (Batch a)

  instance BatchReify a => BatchReify (V.Vector a) where
    newBatchWriter _ =
        withLocalRef
          (unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a))
          $ \_b ->
            generic <$> [java| new BatchWriters.ObjectArrayBatchWriter($_b) |]
    reifyBatch =
        reifyArrayBatch
          -- skipped values do not leave holes in a batch of arrays, so it
          -- is safe to reify all of the values
          (\sz j -> reifyBatch j sz (const True))
          (fmap (fmap return) . V.unsafeSlice)

  instance BatchReflect a => BatchReflect (V.Vector a) where
    newBatchReader _ =
        withLocalRef
          (unsafeUngeneric <$> newBatchReader (Proxy :: Proxy a))
          $ \_b ->
            generic <$> [java| new BatchReaders.ObjectArrayBatchReader($_b) |]
    reflectBatch =
        reflectArrayBatch reflectBatch V.length (return . V.concat)

  instance Batchable a => Batchable (Nullable a) where
    type Batch (Nullable a) =
      'Class "io.tweag.jvm.batching.Tuple2" <>
        '[ 'Array ('Prim "boolean")
         , ArrayBatch (Batch a)
         ]

  instance BatchReify a => BatchReify (Nullable a) where
    newBatchWriter _ =
      withLocalRef
        (unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a))
        $ \_baseBatcher ->
          generic <$> [java| new BatchWriters.NullableBatchWriter($_baseBatcher) |]

    reifyBatch jxs n p = do
      let _batch = unsafeUngeneric jxs
      isnull <- withLocalRef [java| $_batch._1 |] $ \j ->
        V.convert <$> (reify (unsafeCast (j :: JObject)) :: IO (VS.Vector W8Bool))
      let p' i = p i && isnull V.! i == 0
      b <- withLocalRef [java| $_batch._2 |] $ \j0 ->
         (\j -> reifyBatch j n p') (unsafeCast (j0 :: JObject) :: J (Batch a))
      return $ V.zipWith toNullable isnull b
      where
        toNullable :: W8Bool -> a -> Nullable a
        toNullable 0 a = NotNull a
        toNullable _ _ = Null
 |]