jvm-streaming 0.2.2 → 0.3
raw patch · 13 files changed
+451/−218 lines, 13 filesdep +criteriondep +deepseqdep +jvm-batchingdep ~basedep ~inline-javadep ~jnibuild-type:Customsetup-changed
Dependencies added: criterion, deepseq, jvm-batching, vector
Dependency ranges changed: base, inline-java, jni, jvm, streaming
Files
- README.md +35/−0
- Setup.hs +7/−0
- benchmarks/Main.hs +50/−0
- jvm-streaming.cabal +32/−7
- src/Language/Java/Streaming.hs +0/−158
- src/cabal/haskell/Language/Java/Streaming/Jars.hs +4/−0
- src/main/haskell/Language/Java/Streaming.hs +267/−0
- src/test/haskell/Language/Java/StreamingSpec.hs +44/−0
- src/test/haskell/Main.hs +11/−0
- src/test/haskell/Spec.hs +1/−0
- tests/Language/Java/StreamingSpec.hs +0/−44
- tests/Main.hs +0/−8
- tests/Spec.hs +0/−1
README.md view
@@ -1,3 +1,38 @@ # jvm-streaming Expose Java iterators as streams from the streaming package.++## Using it as a dependency++Add `jvm-streaming` to the list of dependencies in your .cabal file.+Then edit the `Setup.hs` file to add some necessary jars to the+classpath.++```Haskell+import Distribution.Simple+import Language.Java.Inline.Cabal+import qualified Language.Java.Streaming.Jars++main = do+ jars <- Language.Java.Streaming.Jars.getJars+ defaultMainWithHooks (addJarsToClasspath jars simpleUserHooks)+```++Add a `custom-setup` stanza to your .cabal file.++```+custom-setup+ setup-depends:+ base,+ Cabal >= 1.24,+ inline-java,+ jvm-streaming+```++## Layout of source directories++This is a multi-language package. We use+Maven's [standard directory layout][maven-sdl] to organize source code+in multiple languages side-by-side.++[maven-sdl]: https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html
+ Setup.hs view
@@ -0,0 +1,7 @@+import Distribution.Simple+import Language.Java.Inline.Cabal+import qualified Language.Java.Batching.Jars++main = do+ jars <- Language.Java.Batching.Jars.getJars+ defaultMainWithHooks (addJarsToClasspath jars simpleUserHooks)
+ benchmarks/Main.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Criterion.Main as Criterion+import Control.DeepSeq+import Data.Int+import qualified Data.Vector.Storable as VS+import Language.Java+import Language.Java.Streaming ()+import Streaming (Of, Stream)+import qualified Streaming.Prelude as Streaming++roundtrip :: forall a. (Reify a, Reflect a) => a -> IO ()+roundtrip a = pushWithSizeHint 30 $ (reflect a >>= reify :: IO a) >> pop++intValues :: [Int32]+intValues = [1..4000]++vValues :: [VS.Vector Int32]+vValues = [ VS.fromList [i, i] | i <- [1..2000] ]++benchBatching :: Benchmark+benchBatching = deepseq intValues $+ bgroup "roundtrips"+ [ bgroup "ints"+ [ bgroup "no batching"+ [ bench "list" $ nfIO $ roundtrip intValues+ ]+ , bgroup "batching"+ [ bench "streams" $ nfIO $ roundtrip+ (Streaming.each intValues :: Stream (Of Int32) IO ())+ ]+ ]+ , bgroup "int vectors"+ [ bgroup "no batching"+ [ bench "list" $ nfIO $ roundtrip vValues+ ]+ , bgroup "batching"+ [ bench "streams" $ nfIO $ roundtrip+ (Streaming.each vValues :: Stream (Of (VS.Vector Int32)) IO ())+ ]+ ]+ ]++main :: IO ()+main =+ withJVM ["-Djava.class.path=../jvm-batching/build/libs/jvm-batching.jar"] $+ Criterion.defaultMain [benchBatching]
jvm-streaming.cabal view
@@ -1,5 +1,5 @@ name: jvm-streaming-version: 0.2.2+version: 0.3 synopsis: Expose Java iterators as streams from the streaming package. description: Please see README.md. homepage: http://github.com/tweag/inline-java/tree/master/jvm-streaming#readme@@ -9,7 +9,7 @@ maintainer: m@tweag.io copyright: 2015-2016 EURL Tweag. category: FFI, JVM, Java-build-type: Simple+build-type: Custom cabal-version: >=1.10 extra-source-files: README.md @@ -18,24 +18,34 @@ location: https://github.com/tweag/inline-java subdir: jvm-streaming +custom-setup+ setup-depends:+ base,+ Cabal >= 1.24,+ inline-java >= 0.6.3,+ jvm-batching >= 0.1 && <0.2+ library- hs-source-dirs: src+ hs-source-dirs: src/main/haskell src/cabal/haskell default-language: Haskell2010 exposed-modules: Language.Java.Streaming+ Language.Java.Streaming.Jars build-depends: base >= 4.7 && < 5, distributed-closure >= 0.3,- jni >= 0.4.0 && <0.6,+ jni >= 0.4.0 && <0.7, jvm >= 0.4.0 && <0.5,- inline-java >= 0.7 && <0.8,+ jvm-batching >= 0.1 && <0.2,+ inline-java >= 0.8 && <0.9, singletons >= 2.2,- streaming >= 0.1.4+ streaming >= 0.1.4,+ vector test-suite spec type: exitcode-stdio-1.0- hs-source-dirs: tests+ hs-source-dirs: src/test/haskell main-is: Main.hs other-modules: Language.Java.StreamingSpec@@ -48,3 +58,18 @@ jvm-streaming, streaming default-language: Haskell2010++benchmark micro-benchmarks+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: benchmarks+ build-depends:+ base >=4.8 && <5,+ criterion,+ deepseq,+ jvm,+ jvm-streaming,+ streaming,+ vector+ default-language: Haskell2010+ ghc-options: -threaded
− src/Language/Java/Streaming.hs
@@ -1,158 +0,0 @@--- | Expose Java iterators as streams from the--- <http://hackage.haskell.org/package/streaming streaming> package.--{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UndecidableInstances #-}--{-# OPTIONS_GHC -fno-warn-orphans #-}-{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}-{-# OPTIONS_GHC -fplugin=Language.Java.Inline.Plugin #-}--module Language.Java.Streaming () where--import Control.Distributed.Closure.TH-import qualified Data.Coerce as Coerce-import Data.IORef (IORef, newIORef, readIORef, writeIORef)-import Data.Int (Int64)-import Data.Singletons (SomeSing(..))-import Data.Word (Word8)-import Foreign.Ptr (FunPtr, Ptr, freeHaskellFunPtr, intPtrToPtr, ptrToIntPtr)-import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)-import qualified Foreign.JNI as JNI-import Foreign.JNI.Types (jnull)-import GHC.Stable- ( castPtrToStablePtr- , castStablePtrToPtr- , deRefStablePtr- , freeStablePtr- , newStablePtr- )-import Language.Java-import Language.Java.Inline-import Streaming (Stream, Of)-import qualified Streaming.Prelude as Streaming--isPoppableStream :: IORef (Stream (Of a) IO ()) -> IO Word8-isPoppableStream ref = do- readIORef ref >>= Streaming.uncons >>= \case- Nothing -> return $ fromIntegral $ fromEnum False- Just (x, stream) -> do- writeIORef ref (Streaming.cons x stream)- return $ fromIntegral $ fromEnum True--popStream :: Reflect a => IORef (Stream (Of a) IO ()) -> IO (J (Interp a))-popStream ref = do- stream <- readIORef ref- Streaming.uncons stream >>= \case- Nothing -> do- exc :: J ('Class "java.util.NoSuchElementException") <- new []- JNI.throw exc- return jnull- Just (x, stream') -> do- writeIORef ref stream'- reflect x--type JNIFun a = JNIEnv -> Ptr JObject -> IO a--foreign import ccall "wrapper" wrapObjectFun- :: JNIFun (Ptr (J ty)) -> IO (FunPtr (JNIFun (Ptr (J ty))))-foreign import ccall "wrapper" wrapBooleanFun- :: JNIFun Word8 -> IO (FunPtr (JNIFun Word8))---- Export only to get a FunPtr.-foreign export ccall "jvm_streaming_freeIterator" freeIterator- :: JNIEnv -> Ptr JObject -> Int64 -> IO ()-foreign import ccall "&jvm_streaming_freeIterator" freeIteratorPtr- :: FunPtr (JNIEnv -> Ptr JObject -> Int64 -> IO ())--data FunPtrTable = forall ty. FunPtrTable- { hasNextPtr :: FunPtr (JNIFun Word8)- , nextPtr :: FunPtr (JNIFun (Ptr (J ty)))- }--freeIterator :: JNIEnv -> Ptr JObject -> Int64 -> IO ()-freeIterator _ _ ptr = do- let sptr = castPtrToStablePtr $ intPtrToPtr $ fromIntegral ptr- FunPtrTable{..} <- deRefStablePtr sptr- freeHaskellFunPtr hasNextPtr- freeHaskellFunPtr nextPtr- freeStablePtr sptr--newIterator- :: Reflect a- => Stream (Of a) IO ()- -> IO (J ('Iface "java.util.Iterator"))-newIterator stream = do- ref <- newIORef stream- hasNextPtr <- wrapBooleanFun $ \_ _ -> isPoppableStream ref- nextPtr <- wrapObjectFun $ \_ _ ->- -- Conversion is safe, because result is always a reflected object.- unsafeForeignPtrToPtr <$> Coerce.coerce <$> popStream ref- -- Keep FunPtr's in a table that can be referenced from the Java side, so- -- that they can be freed.- tblPtr :: Int64 <- fromIntegral . ptrToIntPtr . castStablePtrToPtr <$> newStablePtr FunPtrTable{..}- iterator <-- [java| new java.util.Iterator() {- @Override- public native boolean hasNext();-- @Override- public native Object next();-- @Override- public void remove() {- throw new UnsupportedOperationException();- }-- private native void hsFinalize(long tblPtr);-- @Override- public void finalize() {- hsFinalize($tblPtr);- }- } |]- klass <- JNI.getObjectClass iterator- JNI.registerNatives klass- [ JNI.JNINativeMethod- "hasNext"- (methodSignature [] (sing :: Sing ('Prim "boolean")))- hasNextPtr- , JNI.JNINativeMethod- "next"- (methodSignature [] (sing :: Sing ('Class "java.lang.Object")))- nextPtr- , JNI.JNINativeMethod- "hsFinalize"- (methodSignature [SomeSing (sing :: Sing ('Prim "long"))] (sing :: Sing 'Void))- freeIteratorPtr- ]- return iterator--withStatic [d|- instance Interpretation (Stream (Of a) m r) where- type Interp (Stream (Of a) m r) = 'Iface "java.util.Iterator"-- instance Reify a => Reify (Stream (Of a) IO ()) where- reify itLocal = do- -- We make sure the iterator remains valid while we reference it.- it <- JNI.newGlobalRef itLocal- return $ Streaming.untilRight $ do- call it "hasNext" [] >>= \case- False -> return (Right ())- True -> do- obj <- call it "next" []- Left <$> reify (unsafeCast (obj :: JObject))-- instance Reflect a => Reflect (Stream (Of a) IO ()) where- reflect x = newIterator x- |]
+ src/cabal/haskell/Language/Java/Streaming/Jars.hs view
@@ -0,0 +1,4 @@+-- | Provides the paths to the jars needed to build with jvm-streaming.+module Language.Java.Streaming.Jars (getJars) where++import Language.Java.Batching.Jars
+ src/main/haskell/Language/Java/Streaming.hs view
@@ -0,0 +1,267 @@+-- | Expose Java iterators as streams from the+-- <http://hackage.haskell.org/package/streaming streaming> package.++{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecursiveDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}+{-# OPTIONS_GHC -fplugin=Language.Java.Inline.Plugin #-}++module Language.Java.Streaming+ ( reifyStreamWithBatching+ , reflectStreamWithBatching+ ) where++import Control.Distributed.Closure.TH+import Control.Monad.IO.Class (liftIO)+import qualified Data.Coerce as Coerce+import Data.IORef (newIORef, readIORef, writeIORef)+import Data.Int (Int32, Int64)+import Data.Proxy+import qualified Data.Vector as V+import Data.Singletons (SomeSing(..))+import Foreign.Ptr (FunPtr, Ptr, freeHaskellFunPtr, intPtrToPtr, ptrToIntPtr)+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)+import qualified Foreign.JNI as JNI+import qualified Foreign.JNI.Types as JNI+import Foreign.JNI.Types (jnull)+import GHC.Stable+ ( castPtrToStablePtr+ , castStablePtrToPtr+ , deRefStablePtr+ , freeStablePtr+ , newStablePtr+ )+import Language.Java+import Language.Java.Batching+import Language.Java.Inline+import Streaming (Bifunctor(first), Stream, Of)+import qualified Streaming as Streaming+import qualified Streaming.Prelude as Streaming++imports "io.tweag.jvm.batching.*"+imports "java.util.Iterator"++type JNIFun a = JNIEnv -> Ptr JObject -> IO a++foreign import ccall "wrapper" wrapObjectFun+ :: JNIFun (Ptr (J ty)) -> IO (FunPtr (JNIFun (Ptr (J ty))))++-- Export only to get a FunPtr.+foreign export ccall "jvm_streaming_freeIterator" freeIterator+ :: JNIEnv -> Ptr JObject -> Int64 -> IO ()+foreign import ccall "&jvm_streaming_freeIterator" freeIteratorPtr+ :: FunPtr (JNIEnv -> Ptr JObject -> Int64 -> IO ())++data FunPtrTable = forall ty. FunPtrTable+ { nextPtr :: FunPtr (JNIFun (Ptr (J ty)))+ }++freeIterator :: JNIEnv -> Ptr JObject -> Int64 -> IO ()+freeIterator _ _ ptr = do+ let sptr = castPtrToStablePtr $ intPtrToPtr $ fromIntegral ptr+ FunPtrTable{..} <- deRefStablePtr sptr+ freeHaskellFunPtr nextPtr+ freeStablePtr sptr++-- | Reflects a stream with no batching.+newIterator+ :: forall ty. Stream (Of (J ty)) IO ()+ -> IO (J ('Iface "java.util.Iterator" <> '[ty]))+newIterator stream0 = mdo+ ref <- newIORef stream0+ nextPtr <- wrapObjectFun $ \_ jthis ->+ -- Conversion is safe, because result is always a reflected object.+ unsafeForeignPtrToPtr <$> Coerce.coerce <$> popStream jthis+ -- Keep FunPtr's in a table that can be referenced from the Java side, so+ -- that they can be freed.+ tblPtr :: Int64 <- fromIntegral . ptrToIntPtr . castStablePtrToPtr <$> newStablePtr FunPtrTable{..}+ iterator <-+ [java| new Iterator() {+ /// A field that the Haskell side sets to true when it reaches the end.+ private boolean end = false;+ /// Lookahead element - it always points to a valid element unless+ /// end is true. There is no constructor, so in order to initialize+ // it, next() must be invoked once.+ private Object lookahead;+ @Override+ public boolean hasNext() { return !end; }++ @Override+ public Object next() {+ if (hasNext()) {+ final Object temp = lookahead;+ lookahead = hsNext();+ return temp;+ } else+ throw new java.util.NoSuchElementException();+ }++ @Override+ public void remove() { throw new UnsupportedOperationException(); }++ private native void hsFinalize(long tblPtr);++ private native Object hsNext();++ @Override+ public void finalize() { hsFinalize($tblPtr); }+ } |]+ klass <- JNI.getObjectClass iterator >>= JNI.newGlobalRef+ fieldEndId <- JNI.getFieldID klass "end"+ (JNI.signature (sing :: Sing ('Prim "boolean")))+ let popStream :: Ptr JObject -> IO (J ty)+ popStream ptrThis = do+ stream <- readIORef ref+ Streaming.uncons stream >>= \case+ Nothing -> do+ jthis <- JNI.objectFromPtr ptrThis+ -- When the stream ends, set the end field to True+ -- so the Iterator knows not to call hsNext again.+ JNI.setBooleanField jthis fieldEndId 1+ return jnull+ Just (x, stream') -> do+ writeIORef ref stream'+ return x+ JNI.registerNatives klass+ [ JNI.JNINativeMethod+ "hsNext"+ (methodSignature [] (sing :: Sing ('Class "java.lang.Object")))+ nextPtr+ , JNI.JNINativeMethod+ "hsFinalize"+ (methodSignature [SomeSing (sing :: Sing ('Prim "long"))] (sing :: Sing 'Void))+ freeIteratorPtr+ ]+ -- Call next once to initialize the iterator.+ () <- [java| { $iterator.next(); } |]+ return $ generic iterator++-- | Reifies streams from iterators in batches of the given size.+reifyStreamWithBatching+ :: forall a. BatchReify a+ => Int32 -- ^ The batch size+ -> J ('Iface "java.util.Iterator" <> '[Interp a])+ -> IO (Stream (Of a) IO ())+reifyStreamWithBatching batchSize jiterator0 = do+ let jiterator1 = unsafeUngeneric jiterator0+ jbatcher <- unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a)+ jiterator <- [java| new Iterator() {+ private final int batchSize = $batchSize;+ private final Iterator it = $jiterator1;+ private final BatchWriter batcher = $jbatcher;+ public int count = 0;++ @Override+ public boolean hasNext() { return it.hasNext(); }++ @Override+ public Object next() {+ int i = 0;+ batcher.start(batchSize);+ while (it.hasNext() && i < batchSize) {+ batcher.set(i, it.next());+ i++;+ }+ count = i;+ return batcher.getBatch();+ }++ @Override+ public void remove() {+ throw new UnsupportedOperationException();+ }+ } |]+ >>= JNI.newGlobalRef+ :: IO (J ('Iface "java.util.Iterator"))+ cls <- JNI.getObjectClass jiterator >>= JNI.newGlobalRef+ fieldId <- JNI.getFieldID cls "count"+ (JNI.signature (sing :: Sing ('Prim "int")))++ let go :: Int -- next element to return from the batch+ -> V.Vector a -- current batch of elements+ -> Stream (Of a) IO ()+ go i v =+ if V.length v == i then do+ hasNext <- liftIO [java| $jiterator.hasNext() |]+ if hasNext then do+ v' <- liftIO $+ [java| $jiterator.next() |] `withLocalRef` \jbatch ->+ JNI.getIntField jiterator fieldId+ >>= reifyBatch (unsafeCast (jbatch :: JObject) :: J (Batch a))+ go 0 v'+ else+ liftIO $ do+ JNI.deleteGlobalRef jiterator+ JNI.deleteGlobalRef cls+ else do+ Streaming.yield $ v V.! i+ go (i + 1) v++ return $ go 0 V.empty++-- | Reflects streams to iterators in batches of the given size.+reflectStreamWithBatching+ :: forall a. BatchReflect a+ => Int -- ^ The batch size+ -> Stream (Of a) IO ()+ -> IO (J ('Iface "java.util.Iterator" <> '[Interp a]))+reflectStreamWithBatching batchSize s0 = do+ jiterator <- unsafeUngeneric <$>+ (reflectStream $ Streaming.mapsM+ (\s -> first V.fromList <$> Streaming.toList s)+ $ Streaming.chunksOf batchSize s0+ )+ jbatchReader <- unsafeUngeneric <$> newBatchReader (Proxy :: Proxy a)+ generic <$> [java| new Iterator() {+ private final Iterator it = $jiterator;+ private final BatchReader batchReader = $jbatchReader;+ private int count = 0;++ @Override+ public boolean hasNext() {+ return count < batchReader.getSize() || it.hasNext();+ }+ @Override+ public Object next() {+ if (count == batchReader.getSize()) {+ batchReader.setBatch(it.next());+ count = 0;+ }+ Object o = batchReader.get(count);+ count++;+ return o;+ }+ @Override+ public void remove() {+ throw new UnsupportedOperationException();+ }+ } |]+ where+ reflectStream :: Stream (Of (V.Vector a)) IO ()+ -> IO (J ('Iface "java.util.Iterator" <> '[Batch a]))+ reflectStream = newIterator . Streaming.mapM reflectBatch++withStatic [d|+ instance Interpretation (Stream (Of a) m r) where+ type Interp (Stream (Of a) m r) = 'Iface "java.util.Iterator"++ instance BatchReify a => Reify (Stream (Of a) IO ()) where+ reify = reifyStreamWithBatching 1024 . generic++ instance BatchReflect a => Reflect (Stream (Of a) IO ()) where+ reflect = fmap unsafeUngeneric . reflectStreamWithBatching 1024+ |]
+ src/test/haskell/Language/Java/StreamingSpec.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QuasiQuotes #-}+{-# OPTIONS_GHC -fplugin=Language.Java.Inline.Plugin #-}++module Language.Java.StreamingSpec where++import Data.Int+import Data.IORef+import Language.Java+import Language.Java.Inline+import Language.Java.Streaming ()+import Test.Hspec+import Streaming (Stream, Of)+import qualified Streaming.Prelude as Streaming++spec :: Spec+spec = do+ describe "iteration" $ do+ it "succeeds on empty lists" $ do+ vals <- reflect [1..0 :: Int32]+ iterator <- [java| java.util.Arrays.asList($vals).iterator() |]+ stream <- reify iterator+ Streaming.toList_ stream `shouldReturn` [1..0 :: Int32]+ it "succeeds on singleton lists" $ do+ vals <- reflect [1 :: Int32]+ iterator <- [java| java.util.Arrays.asList($vals).iterator() |]+ stream <- reify iterator+ Streaming.toList_ stream `shouldReturn` [1 :: Int32]+ it "succeeds on non-trivial lists" $ do+ vals <- reflect [1..10000 :: Int32]+ iterator <- [java| java.util.Arrays.asList($vals).iterator() |]+ stream <- reify iterator+ Streaming.toList_ stream `shouldReturn` [1..10000 :: Int32]+ describe "streams" $ do+ it "have the property that reify . reflect == id" $ do+ iterator <- reflect (Streaming.each [1..10000] :: Stream (Of Int32) IO ())+ stream <- reify iterator+ Streaming.toList_ stream `shouldReturn` [1..10000 :: Int32]+ it "don't redo effects" $ do+ ref <- newIORef (1 :: Int32)+ iterator <- reflect $+ Streaming.replicateM 10 $ atomicModifyIORef ref (\i -> (i + 1, i))+ stream <- reify iterator+ Streaming.toList_ stream `shouldReturn` [1..10 :: Int32]
+ src/test/haskell/Main.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Language.Java (withJVM)+import qualified Spec+import Test.Hspec++main :: IO ()+main =+ withJVM ["-Djava.class.path=../jvm-batching/build/libs/jvm-batching.jar"] $+ hspec Spec.spec
+ src/test/haskell/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}
− tests/Language/Java/StreamingSpec.hs
@@ -1,44 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE QuasiQuotes #-}-{-# OPTIONS_GHC -fplugin=Language.Java.Inline.Plugin #-}--module Language.Java.StreamingSpec where--import Data.Int-import Data.IORef-import Language.Java-import Language.Java.Inline-import Language.Java.Streaming ()-import Test.Hspec-import Streaming (Stream, Of)-import qualified Streaming.Prelude as Streaming--spec :: Spec-spec = do- describe "iteration" $ do- it "succeeds on empty lists" $ do- vals <- reflect [1..0 :: Int32]- iterator <- [java| java.util.Arrays.asList($vals).iterator() |]- stream <- reify iterator- Streaming.toList_ stream `shouldReturn` [1..0 :: Int32]- it "succeeds on singleton lists" $ do- vals <- reflect [1 :: Int32]- iterator <- [java| java.util.Arrays.asList($vals).iterator() |]- stream <- reify iterator- Streaming.toList_ stream `shouldReturn` [1 :: Int32]- it "succeeds on non-trivial lists" $ do- vals <- reflect [1..10000 :: Int32]- iterator <- [java| java.util.Arrays.asList($vals).iterator() |]- stream <- reify iterator- Streaming.toList_ stream `shouldReturn` [1..10000 :: Int32]- describe "streams" $ do- it "have the property that reify . reflect == id" $ do- iterator <- reflect (Streaming.each [1..10000] :: Stream (Of Int32) IO ())- stream <- reify iterator- Streaming.toList_ stream `shouldReturn` [1..10000 :: Int32]- it "don't redo effects" $ do- ref <- newIORef (1 :: Int32)- iterator <- reflect $- Streaming.replicateM 10 $ atomicModifyIORef ref (\i -> (i + 1, i))- stream <- reify iterator- Streaming.toList_ stream `shouldReturn` [1..10 :: Int32]
− tests/Main.hs
@@ -1,8 +0,0 @@-module Main where--import Language.Java (withJVM)-import qualified Spec-import Test.Hspec--main :: IO ()-main = withJVM [] $ hspec Spec.spec
− tests/Spec.hs
@@ -1,1 +0,0 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}