lazyio 0.0.1 → 0.0.2
raw patch · 3 files changed
+187/−4 lines, 3 filesdep +transformersdep −mtlnew-component:exe:testPVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
Dependencies removed: mtl
API changes (from Hackage documentation)
Files
- lazyio.cabal +16/−3
- src/System/IO/Lazy.hs +3/−1
- src/Test/Main.hs +168/−0
lazyio.cabal view
@@ -1,5 +1,5 @@ Name: lazyio-Version: 0.0.1+Version: 0.0.2 License: BSD3 License-File: LICENSE Author: Henning Thielemann <haskell@henning-thielemann.de>@@ -23,13 +23,17 @@ Source-Repository this type: darcs location: http://code.haskell.org/~thielema/lazyio/- tag: 0.0.1+ tag: 0.0.2 Flag splitBase description: Choose the new smaller, split-up base package. +Flag buildTests+ description: Build test executables+ default: False+ Library- Build-Depends: mtl >=1.1 && <1.2+ Build-Depends: transformers >=0.0 && <0.2 If flag(splitBase) Build-Depends: base >= 2 Else@@ -42,3 +46,12 @@ Other-Modules: Data.ApplicativeChain System.IO.Lazy.Applicative+++Executable test+ If !flag(buildTests)+ Buildable: False++ GHC-Options: -Wall+ Hs-source-dirs: src+ Main-Is: Test/Main.hs
src/System/IO/Lazy.hs view
@@ -34,6 +34,8 @@ Because only the first line is needed, only the first prompt and the first 'getLine' is executed. +We advise to lift strict IO functions into the lazy IO monad.+Lifting a function like @readFile@ may lead to unintended interleaving. -} module System.IO.Lazy ( T,@@ -41,7 +43,7 @@ ) where import Control.Monad.Trans (MonadIO(liftIO), )-import Control.Monad.State (StateT(StateT), mapStateT, evalStateT, {- runStateT, -} )+import Control.Monad.Trans.State (StateT(StateT), mapStateT, evalStateT, {- runStateT, -} ) import Control.Monad (ap, {- liftM2, -} ) import Control.Applicative (Applicative(pure, (<*>)), ) import System.IO.Unsafe (unsafeInterleaveIO, )
+ src/Test/Main.hs view
@@ -0,0 +1,168 @@+module Main where++import qualified System.IO.Lazy as LazyIO+import qualified System.IO as IO+import Control.Monad.Trans (MonadIO(liftIO), )+import Control.Monad.Trans.Reader (ReaderT, runReaderT, asks, )++import Control.Monad (liftM2, )+import System.Exit (exitFailure, )++import Prelude hiding (getLine, )+++inputName, outputName :: FilePath+inputName = "input"+outputName = "output"++assert :: Bool -> IO ()+assert b =+ if b+ then putStrLn "test ok"+ else putStrLn "failure" >> exitFailure+++class HasReadHandle h where+ getReadHandle :: h -> IO.Handle++class HasWriteHandle h where+ getWriteHandle :: h -> IO.Handle+++newtype ReadHandle = ReadHandle IO.Handle++instance HasReadHandle ReadHandle where+ getReadHandle (ReadHandle h) = h+++data ReadWriteHandle = ReadWriteHandle IO.Handle IO.Handle++instance HasReadHandle ReadWriteHandle where+ getReadHandle (ReadWriteHandle h _) = h++instance HasWriteHandle ReadWriteHandle where+ getWriteHandle (ReadWriteHandle _ h) = h++++getLine :: HasReadHandle h => ReaderT h LazyIO.T String+getLine =+ asks getReadHandle >>= \h -> liftIO $ IO.hGetLine h++getLinePair :: HasReadHandle h => ReaderT h LazyIO.T (String, String)+getLinePair =+ do x <- getLine+ y <- getLine+ return (x,y)++getLinePairOfPairs :: HasReadHandle h =>+ ReaderT h LazyIO.T ((String, String), (String, String))+getLinePairOfPairs =+ do x <- getLinePair+ y <- getLinePair+ return (x,y)++forever :: (Monad m) => m a -> m [a]+forever x = liftM2 (:) x (forever x)++runReadTest ::+ String -> ReaderT ReadHandle LazyIO.T a -> (a -> IO ()) -> IO ()+runReadTest content action check =+ do writeFile inputName content+ IO.withFile inputName IO.ReadMode $ \h ->+ do check =<< LazyIO.run (runReaderT action (ReadHandle h))+ IO.hIsEOF h >>= assert+++putLine :: HasWriteHandle h => String -> ReaderT h LazyIO.T ()+putLine str =+ asks getWriteHandle >>= \h -> liftIO $ IO.hPutStrLn h str++runReadWriteTest ::+ String -> String -> ReaderT ReadWriteHandle LazyIO.T a -> (a -> IO ()) -> IO ()+runReadWriteTest inputContent outputContent action check =+ do writeFile inputName inputContent+ IO.withFile inputName IO.ReadMode $ \hr ->+ IO.withFile outputName IO.WriteMode $ \hw ->+ do check =<< LazyIO.run (runReaderT action (ReadWriteHandle hr hw))+ IO.hIsEOF hr >>= assert+ assert . (outputContent ==) =<< readFile outputName+++{- |+This test works by observing things that should not be observable.+(Yes, in the IO monad it would also be possible to observe memory consumption+and thus whether laziness works or not.)+Be warned again, that you should not use LazyIO in that way,+but restrict its use to a set of IO actions,+that is safe when run lazily.+-}+main :: IO ()+main =+ do runReadTest "bla\n"+ (do a <- getLinePair+ return (fst a))+ (\str -> assert (str == "bla"))++ runReadTest "bla\nblub\n"+ (do a <- getLinePair+ return (snd a))+ (\str -> assert (str == "blub"))++ runReadTest "1\n"+ (do a <- getLinePairOfPairs+ return (fst $ fst a))+ (\str -> assert (str == "1"))++ runReadTest "1\n2\n"+ (do a <- getLinePairOfPairs+ return (snd $ fst a))+ (\str -> assert (str == "2"))++ runReadTest "1\n2\n3\n"+ (do a <- getLinePairOfPairs+ return (fst $ snd a))+ (\str -> assert (str == "3"))++ runReadTest "1\n2\n3\n4\n"+ (do a <- getLinePairOfPairs+ return (snd $ snd a))+ (\str -> assert (str == "4"))++ runReadTest "1\n2\n3\n4\n"+ getLinePairOfPairs+ (\r -> assert (snd (snd r) == "4" && fst (fst r) == "1"))++ runReadTest "1\n2\n3\n4\n"+ getLinePairOfPairs+ (\r ->+ do assert (snd (snd r) == "4")+ assert (snd (fst r) == "2")+ assert (fst (fst r) == "1")+ assert (fst (snd r) == "3"))++ runReadTest "0\n1\n2\n3\n"+ (forever getLine)+ (\r ->+ do assert (r!!2 == "2")+ assert (r!!1 == "1")+ assert (r!!3 == "3")+ assert (r!!0 == "0"))+++ runReadWriteTest "0\n1\n2\n3\n"+ (concat $ replicate 4 "bla\n")+ (forever (putLine "bla" >> getLine))+ (\r ->+ do assert (r!!3 == "3"))++ runReadWriteTest "0\n1\n2\n3\n"+ "a\nb\nc\nd\n"+ (mapM (\c -> putLine [c] >> getLine) ['a'..])+ (\r ->+ do assert (r!!3 == "3"))++ runReadWriteTest "end\n"+ "A\nB\nC\nD\n"+ (mapM (\c -> putLine [c]) ['A'..'D'] >> getLine)+ (\str -> assert (str == "end"))