diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2017, Nikita Volkov
+Copyright (c) 2017, Metrix.AI
 
 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/benchmark/Main.hs b/benchmark/Main.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Main.hs
@@ -0,0 +1,25 @@
+module Main where
+
+import Prelude
+import Criterion.Main
+import Potoki.Core.IO
+import qualified Potoki.Core.Produce as Produce
+import qualified Potoki.Core.Consume as Consume
+import qualified Potoki.Core.Transform as Transform
+
+
+main =
+  defaultMain $
+  [
+    bench "extractLinesConcurrently" $ whnfIO $ produceAndConsume
+      (Produce.fileBytes "data/2.tsv")
+      (right' (Consume.transform
+        (Transform.extractLinesConcurrently numCapabilities)
+        (Consume.count)))
+    ,
+    bench "extractLines" $ whnfIO $ produceAndConsume
+      (Produce.fileBytes "data/2.tsv")
+      (right' (Consume.transform
+        (Transform.extractLines)
+        (Consume.count)))
+  ]
diff --git a/library/Potoki/Core/Consume.hs b/library/Potoki/Core/Consume.hs
--- a/library/Potoki/Core/Consume.hs
+++ b/library/Potoki/Core/Consume.hs
@@ -1,77 +1,354 @@
-module Potoki.Core.Consume where
+module Potoki.Core.Consume
+(
+  Consume(..),
+  apConcurrently,
+  list,
+  sum,
+  transform,
+  count,
+  head,
+  last,
+  reverseList,
+  vector,
+  concat,
+  fold,
+  foldInIO,
+  folding,
+  foldingInIO,
+  execState,
+  writeBytesToStdout,
+  writeBytesToFile,
+  writeBytesToFileWithoutBuffering,
+  appendBytesToFile,
+  deleteFiles,
+  printBytes,
+  printText,
+  printString,
+  parseBytes,
+  parseText,
+  concurrently,
+)
+where
 
-import Potoki.Core.Prelude
+import Potoki.Core.Prelude hiding (sum, head, fold, concat, last)
+import Potoki.Core.Types
 import qualified Potoki.Core.Fetch as A
+import qualified Acquire.IO as B
+import qualified Potoki.Core.Transform as J
+import qualified Potoki.Core.IO.Fetch as L
+import qualified Data.ByteString as C
+import qualified Data.Attoparsec.ByteString as E
+import qualified Data.Attoparsec.Text as F
+import qualified Data.Attoparsec.Types as I
+import qualified Data.Text.IO as K
+import qualified Control.Foldl as D
+import qualified System.Directory as G
+import qualified Potoki.Core.Transform.Concurrency as B
+import qualified Control.Monad.Trans.State.Strict as O
 
 
-{-|
-Active consumer of input into output.
-Sort of like a reducer in Map/Reduce.
-
-Automates the management of resources.
--}
-newtype Consume input output =
-  {-|
-  An action, which executes the provided fetch in IO,
-  while managing the resources behind the scenes.
-  -}
-  Consume (A.Fetch input -> IO output)
-
 instance Profunctor Consume where
   {-# INLINE dimap #-}
   dimap inputMapping outputMapping (Consume consume) =
-    Consume (\ fetch -> fmap outputMapping (consume (fmap inputMapping fetch)))
+    Consume (\ fetch -> fmap outputMapping (consume $ fmap inputMapping fetch))
 
 instance Choice Consume where
+  right' :: Consume a b -> Consume (Either c a) (Either c b)
   right' (Consume rightConsumeIO) =
-    Consume $ \ (A.Fetch eitherFetchIO) -> do
-      fetchedLeftMaybeRef <- newIORef Nothing
-      consumedRight <- 
-        rightConsumeIO $ A.Fetch $ \ nil just -> join $ eitherFetchIO (return nil) $ \ case
-          Right !fetchedRight -> return (just fetchedRight)
-          Left !fetchedLeft -> writeIORef fetchedLeftMaybeRef (Just fetchedLeft) >> return nil
-      fetchedLeftMaybe <- readIORef fetchedLeftMaybeRef
-      case fetchedLeftMaybe of
-        Nothing -> return (Right consumedRight)
-        Just fetchedLeft -> return (Left fetchedLeft)
+     Consume $ \ (Fetch eitherFetchIO) -> do
+       fetchedLeftMaybeRef <- newIORef Nothing
+       consumedRight <-
+         rightConsumeIO $ Fetch $ do
+           eitherFetch <- eitherFetchIO
+           case eitherFetch of
+             Nothing      -> return Nothing
+             Just element -> case element of
+               Right fetchedRight -> return $ Just fetchedRight
+               Left  fetchedLeft  -> do
+                 writeIORef fetchedLeftMaybeRef $ Just fetchedLeft
+                 return Nothing
+       fetchedLeftMaybe <- readIORef fetchedLeftMaybeRef
+       case fetchedLeftMaybe of
+         Nothing          -> return $ Right consumedRight
+         Just fetchedLeft -> return $ Left fetchedLeft 
 
 instance Functor (Consume input) where
   fmap = rmap
 
-instance Applicative (Consume input) where
-  pure x =
-    Consume (const (pure x))
-  (<*>) (Consume leftConsumeIO) (Consume rightConsumeIO) =
-    Consume $ \ fetch -> do
-      (leftFetch, rightFetch) <- A.duplicate fetch
-      rightOutputVar <- newEmptyMVar
-      forkIO $ do
-        !rightOutput <- rightConsumeIO rightFetch
-        putMVar rightOutputVar rightOutput
-      !leftOutput <- leftConsumeIO leftFetch
-      rightOutput <- takeMVar rightOutputVar
-      return (leftOutput rightOutput)
+instance Applicative (Consume a) where
+  pure x = Consume $ \ _ -> pure x
 
+  Consume leftConsumeIO <*> Consume rightConsumeIO =
+    Consume $ \ fetch -> leftConsumeIO fetch <*> rightConsumeIO fetch
+
+instance Monad (Consume a) where
+  Consume leftConsumeIO >>= toRightConsumeIO = Consume $ \ fetch -> do
+    Consume rightConsumeIO <- toRightConsumeIO <$> leftConsumeIO fetch
+    rightConsumeIO fetch
+
+instance MonadIO (Consume a) where
+  liftIO a = Consume $ \ _ -> a
+
+apConcurrently :: Consume a (b -> c) -> Consume a b -> Consume a c
+apConcurrently (Consume leftConsumeIO) (Consume rightConsumeIO) =
+  Consume $ \ fetch -> do
+    (leftFetch, rightFetch) <- A.duplicate fetch
+    rightOutputVar <- newEmptyMVar
+    _ <- forkIO $ do
+      !rightOutput <- rightConsumeIO rightFetch
+      putMVar rightOutputVar rightOutput
+    !leftOutput <- leftConsumeIO leftFetch
+    rightOutput <- takeMVar rightOutputVar
+    return (leftOutput rightOutput)
+
+unit :: Consume a ()
+unit =
+  Consume $ \ _ -> return ()
+
 {-# INLINABLE list #-}
 list :: Consume input [input]
 list =
-  Consume $ \ (A.Fetch fetchIO) ->
-  let
-    build !acc =
-      join
-        (fetchIO
-          (pure (acc []))
-          (\ !element -> build (acc . (:) element)))
-    in build id
+  Consume $ \ (Fetch fetchIO) ->
+    let 
+      build !acc = do
+        fetch <- fetchIO
+        case fetch of
+          Nothing       -> pure $ acc []
+          Just !element -> build $ acc . (:) element
+     in build id
 
 {-# INLINE sum #-}
 sum :: Num num => Consume num num
 sum =
-  Consume $ \ (A.Fetch fetchIO) ->
+  Consume $ \ (Fetch fetchIO) ->
+    let
+      build !acc = do
+        fetch <- fetchIO
+        case fetch of
+          Nothing       -> pure acc
+          Just !element -> build $ element + acc
+     in build 0
+
+{-# INLINABLE transform #-}
+transform :: Transform input1 input2 -> Consume input2 output -> Consume input1 output
+transform (Transform transformAcquire) (Consume consumeIO) =
+  Consume $ \ fetch -> B.acquire (transformAcquire fetch) consumeIO
+
+{-# INLINABLE head #-}
+head :: Consume input (Maybe input)
+head =
+  Consume (\ (A.Fetch fetchIO) -> fetchIO)
+
+{-# INLINABLE last #-}
+last :: Consume input (Maybe input)
+last = 
+  fold D.last 
+
+{-|
+A faster alternative to "list",
+which however constructs the list in the reverse order.
+-}
+{-# INLINABLE reverseList #-}
+reverseList :: Consume input [input]
+reverseList =
+  Consume $ \ (A.Fetch fetchIO) -> build fetchIO []
+  where
+    build fetchIO !acc =
+      fetchIO >>= \case
+        Nothing -> pure acc
+        Just element -> build fetchIO (element : acc)
+
+{-# INLINABLE vector #-}
+vector :: Consume input (Vector input)
+vector =
+  foldInIO D.vectorM
+
+{-# INLINABLE count #-}
+count :: Consume input Int
+count =
+  Consume $ \ (A.Fetch fetchIO) -> let
+    iterate !count = do
+      fetchResult <- fetchIO
+      case fetchResult of
+        Just _ -> iterate (succ count)
+        Nothing -> return count
+    in iterate 0
+
+{-# INLINABLE concat #-}
+concat :: (Semigroup monoid, Monoid monoid) => Consume monoid monoid
+concat =
+  Consume $ \ (A.Fetch fetchIO) -> build fetchIO mempty
+  where
+    build fetchIO !acc =
+      fetchIO >>= \case
+        Nothing -> pure acc
+        Just element -> build fetchIO (acc <> element)
+
+{-# INLINABLE processInIO #-}
+processInIO :: IO () -> (element -> IO ()) -> Consume element ()
+processInIO stop process =
+  Consume (\ fetch -> L.fetchAndHandleAll fetch stop process)
+
+{-# INLINABLE printBytes #-}
+printBytes :: Consume ByteString ()
+printBytes =
+  processInIO (putChar '\n') C.putStr
+
+{-# INLINABLE printText #-}
+printText :: Consume Text ()
+printText =
+  processInIO (putChar '\n') K.putStr
+
+{-# INLINABLE printString #-}
+printString :: Consume String ()
+printString =
+  processInIO (putChar '\n') putStr
+
+{-# INLINABLE writeBytesToStdout #-}
+writeBytesToStdout :: Consume ByteString ()
+writeBytesToStdout =
+  processInIO (return ()) (C.hPut stdout)
+
+{-|
+Overwrite a file.
+
+* Exception-free
+* Automatic resource management
+-}
+{-# INLINABLE writeBytesToFile #-}
+writeBytesToFile :: FilePath -> Consume ByteString (Either IOException ())
+writeBytesToFile =
+  writeBytesToFileWithBuffering (BlockBuffering Nothing)
+
+{-|
+Overwrite a file.
+
+* Exception-free
+* Automatic resource management
+-}
+{-# INLINABLE writeBytesToFileWithBuffering #-}
+writeBytesToFileWithBuffering :: BufferMode -> FilePath -> Consume ByteString (Either IOException ())
+writeBytesToFileWithBuffering bufferMode path =
+  Consume $ \ fetch ->
+  try $ withFile path WriteMode $ \ handle ->
+  do
+    hSetBuffering handle bufferMode
+    L.fetchAndHandleAll fetch (return ()) (C.hPut handle)
+
+{-|
+A more efficient implementation than just writing to file without buffering.
+It uses an explicit buffer of input chunks and flushes all the chunks that have been so far aggregated at once.
+-}
+{-# INLINABLE writeBytesToFileWithoutBuffering #-}
+writeBytesToFileWithoutBuffering :: FilePath -> Consume ByteString (Either IOException ())
+writeBytesToFileWithoutBuffering =
+  transform (arr mconcat . B.bufferizeFlushing 64) . writeBytesToFileWithBuffering NoBuffering
+
+{-|
+Append to a file.
+
+* Exception-free
+* Automatic resource management
+-}
+{-# INLINABLE appendBytesToFile #-}
+appendBytesToFile :: FilePath -> Consume ByteString (Either IOException ())
+appendBytesToFile path =
+  Consume $ \ fetch ->
+  try $ withFile path AppendMode $ \ handleVal ->
+  do
+    L.fetchAndHandleAll fetch (return ()) (C.hPut handleVal)
+
+{-# INLINABLE deleteFiles #-}
+deleteFiles :: Consume FilePath (Either IOException ())
+deleteFiles =
+  Consume $ \ fetch ->
+  try $ L.fetchAndHandleAll fetch (return ()) G.removeFile
+
+{-# INLINABLE fold #-}
+fold :: D.Fold input output -> Consume input output
+fold (D.Fold step initVal finish) =
+  Consume $ \ (A.Fetch fetch) -> build fetch initVal
+  where
+    build fetch !acc =
+      fetch >>= \case
+        Nothing -> pure $ finish acc
+        Just input -> build fetch (step acc input)
+
+{-# INLINABLE foldInIO #-}
+foldInIO :: D.FoldM IO input output -> Consume input output
+foldInIO (D.FoldM step initVal finish) =
+  Consume $ \ (A.Fetch fetch) -> build fetch =<< initVal
+  where
+    build fetch !acc =
+      fetch >>= \case
+        Nothing -> finish acc
+        Just input -> step acc input >>= build fetch
+
+{-# INLINABLE folding #-}
+folding :: D.Fold a b -> Consume a c -> Consume a (b, c)
+folding (D.Fold step initVal extract) (Consume consumeIO) =
+  Consume $ \ fetch -> do
+    foldStateRef <- newIORef initVal
+    consumptionResult <-
+      consumeIO (A.handlingElements (\ element -> do
+        !newState <- flip step element <$> readIORef foldStateRef
+        writeIORef foldStateRef newState) fetch)
+    foldResult <- extract <$> readIORef foldStateRef
+    return (foldResult, consumptionResult)
+
+{-# INLINABLE foldingInIO #-}
+foldingInIO :: D.FoldM IO a b -> Consume a c -> Consume a (b, c)
+foldingInIO (D.FoldM step initVal extract) (Consume consumeIO) =
+  Consume $ \ fetch -> do
+    foldStateRef <- newIORef =<< initVal
+    consumptionResult <-
+      consumeIO (A.handlingElements (\ element -> do
+        !newState <- flip step element =<< readIORef foldStateRef
+        writeIORef foldStateRef newState) fetch)
+    foldResult <- extract =<< readIORef foldStateRef
+    return (foldResult, consumptionResult)
+
+{-# INLINE execState #-}
+execState :: (a -> O.State s b) -> s -> Consume a s
+execState stateFn initialState = 
+  fold $ D.Fold (\currentState input -> snd $ O.runState (stateFn input) currentState) initialState id
+
+{-# INLINABLE runParseResult #-}
+runParseResult :: (Monoid input, Eq input) => (input -> I.IResult input output) -> Consume input (Either Text output)
+runParseResult inputToResult =
+  Consume $ \ (A.Fetch fetchInput) ->
   let
-    build !acc =
-      join
-        (fetchIO
-          (pure acc)
-          (\ !element -> build (element + acc)))
-    in build 0
+    just !input =
+      case inputToResult input of
+        I.Partial newInputToResult -> consume newInputToResult
+        I.Done _ parsed -> return (Right parsed)
+        I.Fail _ contexts message -> return (Left resultMessage)
+          where
+            resultMessage =
+              if null contexts
+                then fromString message
+                else fromString (showString (intercalate " > " contexts) (showString ": " message))
+    consume _ =
+      fetchInput >>= \case
+        Nothing -> just mempty
+        Just !input -> just input
+    in consume inputToResult
+
+{-# INLINABLE parseBytes #-}
+parseBytes :: E.Parser output -> Consume ByteString (Either Text output)
+parseBytes =
+  runParseResult . E.parse
+
+{-# INLINABLE parseText #-}
+parseText :: F.Parser output -> Consume Text (Either Text output)
+parseText =
+  runParseResult . F.parse
+
+{-|
+Execute a Consume concurrently and consume its results.
+-}
+{-# INLINABLE concurrently #-}
+concurrently :: NFData b => Int -> Consume a b -> Consume b c -> Consume a c
+concurrently amount consume1 consume2 =
+  transform (B.concurrently amount (J.consume consume1)) consume2
+  
diff --git a/library/Potoki/Core/Fetch.hs b/library/Potoki/Core/Fetch.hs
--- a/library/Potoki/Core/Fetch.hs
+++ b/library/Potoki/Core/Fetch.hs
@@ -1,47 +1,53 @@
-module Potoki.Core.Fetch where
-
-import Potoki.Core.Prelude
-
+module Potoki.Core.Fetch
+(
+  Fetch(..),
+  duplicate,
+  maybeRef,
+  list,
+  firstCachingSecond,
+  bothFetchingFirst,
+  rightHandlingLeft,
+  rightCachingLeft,
+  eitherFetchingRight,
+  signaling,
+  ioFetch,
+  handleBytes,
+  handleBytesWithChunkSize,
+  handleText,
+  mapFilter,
+  filter,
+  just,
+  takeWhile,
+  infiniteMVar,
+  finiteMVar,
+  vector,
+  handlingElements,
+  lazyByteStringRef,
+  enumUntil,
+)
+where
 
-{-|
-Passive producer of elements.
--}
-newtype Fetch element =
-  {-|
-  Something close to a Church encoding of @IO (Maybe element)@.
-  -}
-  Fetch (forall x. x -> (element -> x) -> IO x)
+import Potoki.Core.Prelude hiding (filter, takeWhile)
+import Potoki.Core.Types
+import qualified Data.Vector as C
+import qualified Data.ByteString as D
+import qualified Data.Text.IO as A
+import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString.Lazy.Internal as B
 
 deriving instance Functor Fetch
 
 instance Applicative Fetch where
   pure x =
-    Fetch (\ nil just -> pure (just x))
-  (<*>) (Fetch leftFn) (Fetch rightFn) =
-    Fetch (\ nil just ->
-      join (leftFn (pure nil) (\ leftElement ->
-        rightFn nil (\ rightElement -> just (leftElement rightElement)))))
-
-instance Monad Fetch where
-  return =
-    pure
-  (>>=) (Fetch leftFn) rightK =
-    Fetch (\ nil just ->
-      join (leftFn (pure nil) (\ leftElement ->
-        case rightK leftElement of
-          Fetch rightFn -> rightFn nil just)))
+    Fetch (pure (Just x))
+  (<*>) (Fetch leftIO) (Fetch rightIO) =
+    Fetch ((<*>) <$> leftIO <*> rightIO)
 
 instance Alternative Fetch where
   empty =
-    Fetch (\ nil just -> pure nil)
-  (<|>) (Fetch leftSignal) (Fetch rightSignal) =
-    Fetch (\ nil just -> join (leftSignal (rightSignal nil just) (pure . just)))
-
-instance MonadPlus Fetch where
-  mzero =
-    empty
-  mplus =
-    (<|>)
+    Fetch (pure Nothing)
+  (<|>) (Fetch leftIO) (Fetch rightIO) =
+    Fetch ((<|>) <$> leftIO <*> rightIO)
 
 {-# INLINABLE duplicate #-}
 duplicate :: Fetch element -> IO (Fetch element, Fetch element)
@@ -53,46 +59,253 @@
     notEndVar <- newTVarIO True
     let
       newFetch ownBuffer mirrorBuffer =
-        Fetch
-          (\ nil just -> do
-            join
-              (atomically
-                (mplus
-                  (do
-                    element <- readTQueue ownBuffer
-                    return (return (just element)))
-                  (do
-                    notEnd <- readTVar notEndVar
-                    if notEnd
-                      then do
-                        notFetching <- readTVar notFetchingVar
-                        guard notFetching
-                        writeTVar notFetchingVar False
-                        return
-                          (join
-                            (fetchIO
-                              (do
-                                atomically
-                                  (do
-                                    writeTVar notEndVar False
-                                    writeTVar notFetchingVar True)
-                                return nil)
-                              (\ !element -> do
-                                atomically
-                                  (do
-                                    writeTQueue mirrorBuffer element
-                                    writeTVar notFetchingVar True)
-                                return (just element))))
-                      else return (return nil)))))
+        Fetch $ do
+          fetch <- fetchIO
+          join $
+            atomically
+             (mplus
+                (do
+                   element <- readTQueue ownBuffer
+                   return $ return (Just element)
+                )
+                (do
+                   notEnd <- readTVar notEndVar
+                   if notEnd
+                     then do
+                       notFetching <- readTVar notFetchingVar
+                       guard notFetching
+                       writeTVar notFetchingVar False
+                       return $ case fetch of
+                         Nothing      -> do
+                           atomically
+                             (do
+                                writeTVar notEndVar False
+                                writeTVar notFetchingVar True
+                             )
+                           return Nothing
+                         Just !element -> do
+                           atomically
+                             (do
+                                writeTQueue mirrorBuffer element
+                                writeTVar notFetchingVar True
+                             )
+                           return $ Just element
+                     else return $ return Nothing
+                )
+             )
       leftFetch =
         newFetch leftBuffer rightBuffer
       rightFetch =
         newFetch rightBuffer leftBuffer
-      in return (leftFetch, rightFetch)
+     in return (leftFetch, rightFetch)
 
+{-# INLINABLE maybeRef #-}
+maybeRef :: IORef (Maybe a) -> Fetch a
+maybeRef refElem =
+  Fetch $ do
+    elemVal <- readIORef refElem
+    case elemVal of
+      Nothing      -> return Nothing
+      Just element -> do
+        writeIORef refElem Nothing
+        return $ Just element
+
 {-# INLINABLE list #-}
 list :: IORef [element] -> Fetch element
 list unsentListRef =
-  Fetch $ \ nil just -> atomicModifyIORef' unsentListRef $ \ case
-    (!head) : tail -> (tail, just head)
-    _ -> ([], nil)
+  Fetch $ do
+    refList <- readIORef unsentListRef
+    case refList of
+      (!headVal) : (!tailVal) -> do
+        writeIORef unsentListRef tailVal
+        return $ Just headVal
+      _ -> do
+        writeIORef unsentListRef []
+        return Nothing
+
+{-# INLINABLE firstCachingSecond #-}
+firstCachingSecond :: IORef b -> Fetch (a, b) -> Fetch a
+firstCachingSecond cacheRef (Fetch bothFetchIO) =
+  Fetch $ do
+    bothFetch <- bothFetchIO
+    case bothFetch of
+      Nothing -> return Nothing
+      Just (!firstVal, !secondVal) -> do
+        writeIORef cacheRef secondVal
+        return $ Just firstVal
+
+{-# INLINABLE bothFetchingFirst #-}
+bothFetchingFirst :: IORef b -> Fetch a -> Fetch (a, b)
+bothFetchingFirst cacheRef (Fetch firstFetchIO) =
+  Fetch $ do
+    firstFetch <- firstFetchIO
+    case firstFetch of
+      Nothing -> return Nothing
+      Just !firstFetched -> do
+        secondCached <- readIORef cacheRef
+        return $ Just (firstFetched, secondCached)
+
+{-# INLINABLE rightHandlingLeft #-}
+rightHandlingLeft :: (left -> IO ()) -> Fetch (Either left right) -> Fetch right
+rightHandlingLeft left2IO (Fetch eitherFetchIO) =
+  Fetch $ do
+    eitherFetch <- eitherFetchIO
+    case eitherFetch of
+      Nothing -> return Nothing
+      Just input -> case input of
+        Right rightInput -> return $ Just rightInput
+        Left  leftInput  -> left2IO leftInput $> Nothing
+
+{-# INLINABLE rightCachingLeft #-}
+rightCachingLeft :: IORef (Maybe left) -> Fetch (Either left right) -> Fetch right
+rightCachingLeft cacheRef =
+  rightHandlingLeft (writeIORef cacheRef . Just)
+
+{-# INLINABLE eitherFetchingRight #-}
+eitherFetchingRight :: IORef (Maybe left) -> Fetch right -> Fetch (Either left right)
+eitherFetchingRight cacheRef (Fetch rightFetchIO) =
+  Fetch $ do
+    rightFetch <- rightFetchIO
+    case rightFetch of
+      Nothing -> return Nothing
+      Just r  -> atomicModifyIORef' cacheRef $ \ case
+        Nothing -> (Nothing, Just $ Right r)
+        Just l  -> (Nothing, Just $ Left  l)
+
+{-# INLINABLE signaling #-}
+signaling :: IO () -> IO () -> Fetch a -> Fetch a
+signaling signalEnd signalElement (Fetch fetchIO) =
+  Fetch $ do
+    fetch <- fetchIO
+    case fetch of
+      Nothing      -> signalEnd $> Nothing
+      Just element -> signalElement >> return (Just element)
+
+{-# INLINABLE ioFetch #-}
+ioFetch :: IO (Fetch a) -> Fetch a
+ioFetch fetchIO =
+  Fetch $ do
+    Fetch fetch <- fetchIO
+    fetch
+
+{-# INLINABLE handleBytes #-}
+handleBytes :: Handle -> Fetch (Either IOException ByteString)
+handleBytes =
+  handleBytesWithChunkSize ioChunkSize
+
+{-# INLINABLE handleBytesWithChunkSize #-}
+handleBytesWithChunkSize :: Int -> Handle -> Fetch (Either IOException ByteString)
+handleBytesWithChunkSize chunkSize handleVal =
+  Fetch $ do
+    chunk <- try (D.hGetSome handleVal chunkSize)
+    case chunk of
+      Right "" -> return Nothing
+      _ -> return (Just chunk)
+
+{-# INLINABLE handleText #-}
+handleText :: Handle -> Fetch (Either IOException Text)
+handleText handleVal =
+  Fetch $ do
+    chunk <- try (A.hGetChunk handleVal)
+    case chunk of
+      Right "" -> return Nothing
+      _ -> return (Just chunk)
+
+{-# INLINABLE mapFilter #-}
+mapFilter :: (input -> Maybe output) -> Fetch input -> Fetch output
+mapFilter mapping (Fetch fetchIO) =
+  {-# SCC "mapFilter" #-} 
+  Fetch $ let
+    loop = do 
+      fetch <- fetchIO
+      case mapping <$> fetch of
+        Just (Just !output) -> return (Just output)
+        Just Nothing -> loop
+        Nothing -> return Nothing
+    in loop
+
+{-# INLINABLE filter #-}
+filter :: (input -> Bool) -> Fetch input -> Fetch input
+filter predicate (Fetch fetchIO) =
+  Fetch $ let
+    loop = do 
+      fetch <- fetchIO
+      case predicate <$> fetch of
+        Just True -> return fetch
+        Just False -> loop
+        Nothing -> return Nothing
+    in loop
+
+{-# INLINABLE just #-}
+just :: Fetch (Maybe element) -> Fetch element
+just (Fetch fetchIO) =
+  {-# SCC "just" #-} 
+  Fetch $ let
+    loop = do
+      fetch <- fetchIO
+      case fetch of
+        Just (Just !element) -> return (Just element)
+        Just (Nothing) -> loop
+        Nothing -> return Nothing
+    in loop
+
+{-# INLINABLE takeWhile #-}
+takeWhile :: (element -> Bool) -> Fetch element -> Fetch element
+takeWhile predicate (Fetch fetchIO) =
+  Fetch $ do
+    fetch <- fetchIO
+    case predicate <$> fetch of
+      Just True -> return fetch
+      _ -> return Nothing
+
+{-# INLINABLE infiniteMVar #-}
+infiniteMVar :: MVar element -> Fetch element
+infiniteMVar var =
+  Fetch $ fmap Just $ takeMVar var
+
+{-# INLINABLE finiteMVar #-}
+finiteMVar :: MVar (Maybe element) -> Fetch element
+finiteMVar var =
+  Fetch $ takeMVar var
+
+{-# INLINABLE vector #-}
+vector :: IORef Int -> Vector element -> Fetch element
+vector indexRef vectorVal =
+  Fetch $ do
+    indexVal <- readIORef indexRef
+    if indexVal < C.length vectorVal
+      then do
+        writeIORef indexRef (succ indexVal)
+        return (Just (C.unsafeIndex vectorVal indexVal))
+      else return Nothing
+
+{-# INLINABLE handlingElements #-}
+handlingElements :: (element -> IO ()) -> Fetch element -> Fetch element
+handlingElements xRay (Fetch fetchIO) =
+  Fetch $ do
+    mbElement <- fetchIO
+    case mbElement of
+      Just !element -> xRay element $> mbElement
+      Nothing -> return Nothing
+
+{-# INLINE lazyByteStringRef #-}
+lazyByteStringRef :: IORef B.ByteString -> Fetch ByteString
+lazyByteStringRef ref =
+  Fetch $ do
+    lazyByteString <- readIORef ref
+    case lazyByteString of
+      B.Chunk chunk remainders -> do
+        writeIORef ref remainders
+        return (Just chunk)
+      B.Empty -> return Nothing
+
+{-# INLINE enumUntil #-}
+enumUntil :: (Enum a, Ord a) => IORef a -> a -> Fetch a
+enumUntil ref maxIndex =
+  Fetch $ do
+    index <- readIORef ref
+    if index <= maxIndex
+      then do
+        writeIORef ref (succ index)
+        return (Just index)
+      else return Nothing
diff --git a/library/Potoki/Core/IO.hs b/library/Potoki/Core/IO.hs
--- a/library/Potoki/Core/IO.hs
+++ b/library/Potoki/Core/IO.hs
@@ -1,40 +1,43 @@
-module Potoki.Core.IO where
+module Potoki.Core.IO (
+  produceAndConsume,
+  produceAndTransformAndConsume,
+  produce,
+  consume,
+  transformList,
+) where
 
 import Potoki.Core.Prelude
+import Potoki.Core.Types
 import qualified Potoki.Core.Produce as A
 import qualified Potoki.Core.Consume as B
-import qualified Potoki.Core.Transform as C
-import qualified Potoki.Core.Fetch as D
+import qualified Acquire.IO as C
 
 
-produceAndConsume :: A.Produce input -> B.Consume input output -> IO output
-produceAndConsume (A.Produce produce) (B.Consume consume) =
-  do
-    (fetch, kill) <- produce
-    consume fetch <* kill
-
-produceAndTransformAndConsume :: A.Produce input -> C.Transform input anotherInput -> B.Consume anotherInput output -> IO output
-produceAndTransformAndConsume (A.Produce produce) (C.Transform transform) (B.Consume consume) =
-  do
-    (fetch, kill) <- produce
-    (transform fetch >>= consume) <* kill
+produceAndConsume :: Produce input -> Consume input output -> IO output
+produceAndConsume (Produce produceAcquire) (Consume consumeIO) =
+  C.acquire produceAcquire consumeIO
 
-produce :: A.Produce input -> forall x. IO x -> (input -> IO x) -> IO x
-produce (A.Produce produce) stop emit =
-  do
-    (D.Fetch fetchIO, kill) <- produce
-    fix (\ loop -> join (fetchIO stop (\ element -> emit element >> loop))) <* kill
+produceAndTransformAndConsume :: Produce input -> Transform input anotherInput -> Consume anotherInput output -> IO output
+produceAndTransformAndConsume (Produce produceAcquire) (Transform transformAcquire) (Consume consumeIO) =
+  C.acquire (produceAcquire >>= transformAcquire) consumeIO
 
-consume :: (forall x. x -> (input -> x) -> IO x) -> B.Consume input output -> IO output
-consume fetch (B.Consume consume) =
-  consume (D.Fetch fetch)
+produce :: Produce input -> forall x. IO x -> (input -> IO x) -> IO x
+produce (Produce produceAcquire) stop emit =
+  C.acquire produceAcquire $ \ (Fetch fetchIO) -> let
+    loop = do
+      fetch <- fetchIO
+      case fetch of
+        Nothing      -> stop
+        Just element -> emit element >> loop
+    in loop
 
-{-| Fetch all the elements running the provided handler on them -}
-fetchAndHandleAll :: D.Fetch element -> IO () -> (element -> IO ()) -> IO ()
-fetchAndHandleAll (D.Fetch fetchIO) onEnd onElement =
-  fix (\ loop -> join (fetchIO onEnd (\ element -> onElement element >> loop)))
+consume :: IO (Maybe input) -> Consume input output -> IO output
+consume fetchIO (Consume consumeIO) =
+  consumeIO $ Fetch fetchIO
 
-{-| Fetch just one element -}
-fetch :: D.Fetch element -> IO (Maybe element)
-fetch (D.Fetch fetchIO) =
-  fetchIO Nothing Just
+transformList :: Transform a b -> [a] -> IO [b]
+transformList transform inputList =
+  produceAndTransformAndConsume
+    (A.list inputList)
+    transform
+    (B.list)
diff --git a/library/Potoki/Core/IO/Fetch.hs b/library/Potoki/Core/IO/Fetch.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/IO/Fetch.hs
@@ -0,0 +1,24 @@
+module Potoki.Core.IO.Fetch where
+
+import Potoki.Core.Prelude
+import Potoki.Core.Types
+
+{-| Fetch all the elements running the provided handler on them -}
+fetchAndHandleAll :: Fetch element -> IO () -> (element -> IO ()) -> IO ()
+fetchAndHandleAll (Fetch fetchIO) onEnd onElement =
+  let
+    loop = do
+      fetch <- fetchIO
+      case fetch of
+        Nothing      -> onEnd
+        Just element -> onElement element >> loop
+    in loop
+
+{-| Fetch and handle just one element -}
+fetchAndHandle :: Fetch element -> IO a -> (element -> IO a) -> IO a
+fetchAndHandle (Fetch fetchIO) onEnd onElement =
+  do
+    fetch <- fetchIO
+    case fetch of
+      Nothing      -> onEnd
+      Just element -> onElement element
diff --git a/library/Potoki/Core/Prelude.hs b/library/Potoki/Core/Prelude.hs
--- a/library/Potoki/Core/Prelude.hs
+++ b/library/Potoki/Core/Prelude.hs
@@ -1,6 +1,10 @@
 module Potoki.Core.Prelude
 ( 
   module Exports,
+  ioChunkSize,
+  textString,
+  unsnoc,
+  mapLeft,
 )
 where
 
@@ -12,6 +16,7 @@
 import Control.Concurrent as Exports
 import Control.Exception as Exports
 import Control.Monad as Exports hiding (mapM_, sequence_, forM_, msum, mapM, sequence, forM)
+import Control.Monad.IO.Class as Exports
 import Control.Monad.Fix as Exports hiding (fix)
 import Control.Monad.ST as Exports
 import Data.Bits as Exports
@@ -31,10 +36,11 @@
 import Data.Ix as Exports
 import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')
 import Data.Maybe as Exports
-import Data.Monoid as Exports
+import Data.Monoid as Exports hiding (Last(..), First(..), (<>))
 import Data.Ord as Exports
 import Data.Proxy as Exports
 import Data.Ratio as Exports
+import Data.Semigroup as Exports
 import Data.STRef as Exports
 import Data.String as Exports
 import Data.Traversable as Exports
@@ -46,7 +52,7 @@
 import Foreign.ForeignPtr as Exports
 import Foreign.Ptr as Exports
 import Foreign.StablePtr as Exports
-import Foreign.Storable as Exports
+import Foreign.Storable as Exports hiding (sizeOf, alignment)
 import GHC.Conc as Exports hiding (withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)
 import GHC.Exts as Exports (lazy, inline, sortWith, groupWith)
 import GHC.Generics as Exports (Generic)
@@ -55,7 +61,7 @@
 import Prelude as Exports hiding (concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.))
 import System.Environment as Exports
 import System.Exit as Exports
-import System.IO as Exports (Handle, hClose)
+import System.IO as Exports
 import System.IO.Error as Exports
 import System.IO.Unsafe as Exports
 import System.Mem as Exports
@@ -76,3 +82,81 @@
 -- stm
 -------------------------
 import Control.Concurrent.STM as Exports
+
+-- acquire
+-------------------------
+import Acquire.Acquire as Exports
+
+-- text
+-------------------------
+import Data.Text as Exports (Text)
+
+-- bytestring
+-------------------------
+import Data.ByteString as Exports (ByteString)
+
+-- unordered-containers
+-------------------------
+import Data.HashMap.Strict as Exports (HashMap)
+
+-- vector
+-------------------------
+import Data.Vector as Exports (Vector)
+
+-- time
+-------------------------
+import Data.Time as Exports
+
+-- hashable
+-------------------------
+import Data.Hashable as Exports (Hashable)
+
+-- primitive
+-------------------------
+import Data.Primitive as Exports
+
+-- deepseq
+-------------------------
+import Control.DeepSeq as Exports
+
+-- deferred-folds
+-------------------------
+import DeferredFolds.Unfoldr as Exports (Unfoldr(..))
+
+-- stm-chans
+-------------------------
+import Control.Concurrent.STM.TBMChan as Exports
+import Control.Concurrent.STM.TMChan as Exports
+
+--------------------------------------------------------------------------------
+
+import qualified Data.Text as A
+
+{-# NOINLINE ioChunkSize #-}
+ioChunkSize :: Int
+ioChunkSize =
+  shiftL 2 12
+
+textString :: Text -> String
+textString =
+  A.unpack
+
+{-# INLINABLE unsnoc #-}
+unsnoc :: [a] -> Maybe ([a], a)
+unsnoc listVal =
+  case process listVal of
+    (initVal, lastMaybe) -> fmap (\ lastVal -> (initVal, lastVal)) lastMaybe
+  where
+    process listVal' =
+      case listVal' of
+        headVal : tailVal -> case tailVal of
+          [] -> ([], Just headVal)
+          _ -> case process tailVal of
+            (initVal, lastMaybe) -> (headVal : initVal, lastMaybe)
+        _ -> ([], Nothing)
+
+{-# INLINE mapLeft #-}
+mapLeft :: (oldLeft -> newLeft) -> Either oldLeft right -> Either newLeft right
+mapLeft f = \ case
+  Left a -> Left (f a)
+  Right b -> Right b
diff --git a/library/Potoki/Core/Produce.hs b/library/Potoki/Core/Produce.hs
--- a/library/Potoki/Core/Produce.hs
+++ b/library/Potoki/Core/Produce.hs
@@ -1,40 +1,328 @@
-module Potoki.Core.Produce where
+module Potoki.Core.Produce
+(
+  Produce(..),
+  list,
+  transform,
+  vector,
+  vectorWithIndices,
+  hashMapRows,
+  fileBytes,
+  fileBytesAtOffset,
+  fileText,
+  stdinBytes,
+  directoryContents,
+  finiteMVar,
+  infiniteMVar,
+  tbmChan,
+  tmChan,
+  lazyByteString,
+  enumInRange,
+  mergeOrdering,
+  unfoldr,
+)
+where
 
-import Potoki.Core.Prelude
+import Potoki.Core.Prelude hiding (unfoldr)
+import Potoki.Core.Types
 import qualified Potoki.Core.Fetch as A
+import qualified Data.HashMap.Strict as B
+import qualified Data.Vector as C
+import qualified Data.Vector.Generic as GenericVector
+import qualified System.Directory as G
+import qualified Acquire.Acquire as M
+import qualified Data.ByteString.Lazy as D
 
 
-{-|
-Passive producer of elements with support for early termination.
-
-Automates the management of resources.
--}
-newtype Produce element =
-  Produce (IO (A.Fetch element, IO ()))
-
 deriving instance Functor Produce
 
 instance Applicative Produce where
-  pure x =
-    list [x]
-  (<*>) (Produce leftIO) (Produce rightIO) =
-    Produce $ do
-      (leftFetch, leftKill) <- leftIO
-      (rightFetch, rightKill) <- rightIO
-      return (leftFetch <*> rightFetch, leftKill >> rightKill)
+  pure x = Produce $ do
+    refX <- liftIO (newIORef (Just x))
+    return (A.maybeRef refX)
+  (<*>) (Produce leftAcquire) (Produce rightAcquire) =
+    Produce ((<*>) <$> leftAcquire <*> rightAcquire)
 
 instance Alternative Produce where
   empty =
-    Produce (pure (empty, pure ()))
-  (<|>) (Produce leftIO) (Produce rightIO) =
-    Produce $ do
-      (leftFetch, leftKill) <- leftIO
-      (rightFetch, rightKill) <- rightIO
-      return (leftFetch <|> rightFetch, leftKill >> rightKill)
+    Produce (pure empty)
+  (<|>) (Produce leftAcquire) (Produce rightAcquire) =
+    Produce ((<|>) <$> leftAcquire <*> rightAcquire)
 
+instance Monad Produce where
+  return = pure
+  (>>=) (Produce (Acquire io1)) k2 =
+    Produce $ Acquire $ do
+      (A.Fetch fetch1, release1) <- io1
+      release2Ref <- newIORef (return ())
+      fetch3Var <- newIORef (return Nothing)
+      let
+        fetch2 input1 =
+          case k2 input1 of
+            Produce (Acquire io2) -> do
+              join (readIORef release2Ref)
+              (A.Fetch fetch2', release2') <- io2
+              writeIORef release2Ref release2'
+              return fetch2'
+        release3 =
+          join (readIORef release2Ref) >> release1
+        fetch3 =  do
+            res <- readIORef fetch3Var
+            mayY <- res
+            case mayY of
+              Nothing -> do
+                mayX <- fetch1
+                case mayX of
+                  Nothing -> return Nothing
+                  Just x -> do
+                    fetch2 x >>= writeIORef fetch3Var
+                    fetch3
+              Just y  -> return $ Just y
+      return (A.Fetch fetch3, release3)
+
+instance MonadIO Produce where
+  liftIO io = Produce . liftIO $ do
+    refX <- newIORef $ Just io
+    let fetch = A.Fetch $ fetchIO refX
+          where
+            fetchIO ref = do
+              elemVal <- readIORef ref
+              for elemVal $ \getElement -> do
+                  writeIORef ref Nothing
+                  getElement
+    return fetch
+
+instance Semigroup (Produce a) where
+  (<>) = (<|>)
+
+instance Monoid (Produce a) where
+  mempty = empty
+  mappend = (<>)
+
 {-# INLINABLE list #-}
 list :: [input] -> Produce input
-list list =
+list inputList =
+  Produce $ liftIO (A.list <$> newIORef inputList)
+
+{-# INLINE transform #-}
+transform :: Transform input output -> Produce input -> Produce output
+transform (Transform transformAcquire) (Produce produceAcquire) =
   Produce $ do
-    unsentListRef <- newIORef list
-    return (A.list unsentListRef, return ())
+    fetch <- produceAcquire
+    transformAcquire fetch
+
+{-# INLINE vector #-}
+vector :: GenericVector.Vector vector input => vector input -> Produce input
+vector vectorVal =
+  Produce $ liftIO $ do
+    indexRef <- newIORef 0
+    let
+      fetch =
+        A.Fetch $ do
+          indexVal <- readIORef indexRef
+          writeIORef indexRef $! succ indexVal
+          return $ (GenericVector.!?) vectorVal indexVal
+      in return fetch
+
+{-# INLINE vectorWithIndices #-}
+vectorWithIndices :: GenericVector.Vector vector a => vector a -> Produce (Int, a)
+vectorWithIndices vectorVal =
+  Produce $ liftIO $ do
+    indexRef <- newIORef 0
+    let
+      fetch =
+        A.Fetch $ do
+          indexVal <- readIORef indexRef
+          writeIORef indexRef $! succ indexVal
+          return $ fmap (indexVal,) $ (GenericVector.!?) vectorVal indexVal
+      in return fetch
+
+{-# INLINE hashMapRows #-}
+hashMapRows :: HashMap a b -> Produce (a, b)
+hashMapRows =
+  list . B.toList
+
+{-|
+Read from a file by path.
+
+* Exception-free
+* Automatic resource management
+-}
+{-# INLINABLE fileBytes #-}
+fileBytes :: FilePath -> Produce (Either IOException ByteString)
+fileBytes path =
+  accessingHandle (openBinaryFile path ReadMode) A.handleBytes
+
+{-|
+Read from a file by path.
+
+* Exception-free
+* Automatic resource management
+-}
+{-# INLINABLE fileBytesAtOffset #-}
+fileBytesAtOffset :: FilePath -> Int -> Produce (Either IOException ByteString)
+fileBytesAtOffset path offset =
+  accessingHandle acquire A.handleBytes
+  where
+    acquire =
+      do
+        handleVal <- openBinaryFile path ReadMode
+        hSeek handleVal AbsoluteSeek (fromIntegral offset)
+        return handleVal
+
+{-# INLINABLE accessingHandle #-}
+accessingHandle :: IO Handle -> (Handle -> A.Fetch (Either IOException a)) -> Produce (Either IOException a)
+accessingHandle acquireHandle fetch =
+  Produce $ M.Acquire (catchIOError normal failing)
+  where
+    normal =
+      do
+        handleVal <- acquireHandle
+        return (fetch handleVal, catchIOError (hClose handleVal) (const (return ())))
+    failing exception =
+      return (pure (Left exception), return ())
+
+{-# INLINABLE stdinBytes #-}
+stdinBytes :: Produce (Either IOException ByteString)
+stdinBytes =
+  Produce $ M.Acquire (return (A.handleBytes stdin, return ()))
+
+{-|
+Sorted subpaths of the directory.
+-}
+{-# INLINABLE directoryContents #-}
+directoryContents :: FilePath -> Produce (Either IOException FilePath)
+directoryContents path =
+  Produce $ M.Acquire (catchIOError success failure)
+  where
+    success =
+      do
+        subPaths <- G.listDirectory path
+        ref <- newIORef (map (Right . mappend path . (:) '/') (sort subPaths))
+        return (A.list ref, return ())
+    failure exception =
+      return (pure (Left exception), return ())
+
+{-|
+Read from a file by path.
+
+* Exception-free
+* Automatic resource management
+-}
+{-# INLINABLE fileText #-}
+fileText :: FilePath -> Produce (Either IOException Text)
+fileText path =
+  Produce $ M.Acquire (catchIOError success failure)
+  where
+    success =
+      do
+        handleVal <- openFile path ReadMode
+        return (A.handleText handleVal, catchIOError (hClose handleVal) (const (return ())))
+    failure exception =
+      return (pure (Left exception), return ())
+
+{-|
+Read from MVar.
+Nothing gets interpreted as the end of input.
+-}
+{-# INLINE finiteMVar #-}
+finiteMVar :: MVar (Maybe element) -> Produce element
+finiteMVar var =
+  Produce $ M.Acquire (return (A.finiteMVar var, return ()))
+
+{-|
+Read from MVar.
+Never stops.
+-}
+{-# INLINE infiniteMVar #-}
+infiniteMVar :: MVar element -> Produce element
+infiniteMVar var =
+  Produce $ M.Acquire (return (A.infiniteMVar var, return ()))
+
+{-# INLINE tbmChan #-}
+tbmChan :: TBMChan element -> Produce element
+tbmChan chan = do
+  Produce $ return $ Fetch $ atomically $ readTBMChan chan
+
+{-# INLINE tmChan #-}
+tmChan :: TMChan element -> Produce element
+tmChan chan = do
+  Produce $ return $ Fetch $ atomically $ readTMChan chan
+
+{-# INLINE lazyByteString #-}
+lazyByteString :: D.ByteString -> Produce ByteString
+lazyByteString lbs =
+  Produce $ M.Acquire $ do
+    ref <- newIORef lbs
+    return (A.lazyByteStringRef ref, return ())
+
+{-# INLINE enumInRange #-}
+enumInRange :: (Enum a, Ord a) => a -> a -> Produce a
+enumInRange from to =
+  Produce $ M.Acquire $ do
+    ref <- newIORef from
+    return (A.enumUntil ref to, return ())
+
+{-|
+Merge two ordered sequences into one
+-}
+mergeOrdering :: (a -> a -> Bool) -> Produce a -> Produce a -> Produce a
+mergeOrdering compare (Produce produceLeft) (Produce produceRight) = Produce $ do
+  Fetch fetchLeft <- produceLeft
+  Fetch fetchRight <- produceRight
+  leftCache <- liftIO $ newIORef Nothing
+  rightCache <- liftIO $ newIORef Nothing
+  return $ Fetch $ do
+    cachedLeftMaybe <- readIORef leftCache
+    case cachedLeftMaybe of
+      Just left -> do
+        fetchedRightMaybe <- fetchRight
+        case fetchedRightMaybe of
+          Just right -> if compare left right
+            then do
+              writeIORef leftCache Nothing
+              writeIORef rightCache (Just right)
+              return (Just left)
+            else return (Just right)
+          Nothing -> do
+            writeIORef leftCache Nothing
+            return (Just left)
+      Nothing -> do
+        fetchedLeftMaybe <- fetchLeft
+        case fetchedLeftMaybe of
+          Just left -> do
+            cachedRightMaybe <- readIORef rightCache
+            case cachedRightMaybe of
+              Just right -> if compare left right
+                then return (Just left)
+                else do
+                  writeIORef leftCache (Just left)
+                  writeIORef rightCache Nothing
+                  return (Just right)
+              Nothing -> do
+                fetchedRightMaybe <- fetchRight
+                case fetchedRightMaybe of
+                  Just right -> if compare left right
+                    then do
+                      writeIORef rightCache (Just right)
+                      return (Just left)
+                    else do
+                      writeIORef leftCache (Just left)
+                      return (Just right)
+                  Nothing -> return (Just left)
+          Nothing -> do
+            cachedRightMaybe <- readIORef rightCache
+            case cachedRightMaybe of
+              Just right -> do
+                writeIORef rightCache Nothing
+                return (Just right)
+              Nothing -> fetchRight
+
+unfoldr :: Unfoldr a -> Produce a
+unfoldr (Unfoldr unfoldr) = Produce $ liftIO $ do
+  fetchRef <- newIORef (return Nothing)
+  writeIORef fetchRef $ let
+    step !a fetchNext = do
+      writeIORef fetchRef fetchNext
+      return (Just a)
+    in unfoldr step (return Nothing)
+  return $ Fetch $ join $ readIORef fetchRef
diff --git a/library/Potoki/Core/TextBuilder.hs b/library/Potoki/Core/TextBuilder.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/TextBuilder.hs
@@ -0,0 +1,20 @@
+module Potoki.Core.TextBuilder
+where
+
+import Potoki.Core.Prelude
+import Text.Builder
+
+
+count :: Int -> Builder
+count = thousandSeparatedUnsignedDecimal ','
+
+streamProgressMessage :: Int -> Int -> Builder
+streamProgressMessage progress total =
+  "Processed " <> count progress <> " elements (" <>
+  count total <> " in total)"
+
+indexationSummary :: Int -> Int -> Builder
+indexationSummary userAmount uriAmount =
+  "Got the following amounts of nodes:\n" <>
+  "\tUsers: " <> count userAmount <> "\n" <>
+  "\tUris: " <> count uriAmount
diff --git a/library/Potoki/Core/Transform.hs b/library/Potoki/Core/Transform.hs
--- a/library/Potoki/Core/Transform.hs
+++ b/library/Potoki/Core/Transform.hs
@@ -1,148 +1,70 @@
-module Potoki.Core.Transform where
-
-import Potoki.Core.Prelude
-import qualified Potoki.Core.Fetch as A
-import qualified Potoki.Core.Consume as C
-import qualified Potoki.Core.Produce as D
-import qualified Deque as B
-
-
-newtype Transform input output =
-  Transform (A.Fetch input -> IO (A.Fetch output))
-
-instance Category Transform where
-  id =
-    Transform return
-  (.) (Transform leftFetchIO) (Transform rightFetchIO) =
-    Transform (leftFetchIO <=< rightFetchIO)
-
-instance Profunctor Transform where
-  dimap inputMapping outputMapping (Transform fetchIO) =
-    Transform (\ inputFetch -> (fmap . fmap) outputMapping (fetchIO (fmap inputMapping inputFetch)))
-
-instance Choice Transform where
-  right' (Transform rightTransformIO) =
-    Transform $ \ (A.Fetch eitherFetchIO) -> do
-      fetchedLeftMaybeRef <- newIORef Nothing
-      let
-        createRightFetchIO =
-          rightTransformIO $ A.Fetch $ \ nil just -> join $ eitherFetchIO (return nil) $ \ case
-            Right !rightInput -> return (just rightInput)
-            Left !leftInput -> writeIORef fetchedLeftMaybeRef (Just leftInput) $> nil
-      rightFetchIORef <- newIORef =<< createRightFetchIO
-      return $ A.Fetch $ \ nil just -> do
-        A.Fetch rightFetchIO <- readIORef rightFetchIORef
-        join $ rightFetchIO
-          (do
-            fetchedLeftMaybe <- readIORef fetchedLeftMaybeRef
-            case fetchedLeftMaybe of
-              Just fetchedLeft -> do
-                writeIORef fetchedLeftMaybeRef Nothing
-                writeIORef rightFetchIORef =<< createRightFetchIO
-                return (just (Left fetchedLeft))
-              Nothing -> return nil)
-          (\ right -> return (just (Right right)))
-
-instance Strong Transform where
-  first' (Transform firstTransformIO) =
-    Transform $ \ (A.Fetch bothFetchIO) -> do
-      secondFetchedDequeRef <- newIORef mempty
-      A.Fetch firstFetchIO <-
-        firstTransformIO $ A.Fetch $ \ nil just ->
-        join $ bothFetchIO (return nil) $ \ (!firstFetched, !secondFetched) -> do
-          modifyIORef' secondFetchedDequeRef (B.snoc secondFetched)
-          return (just firstFetched)
-      return $ A.Fetch $ \ nil just -> join $ firstFetchIO (return nil) $ \ !firstFetched -> do
-        secondFetchedDeque <- readIORef secondFetchedDequeRef
-        case B.uncons secondFetchedDeque of
-          Just (!secondFetched, !secondFetchedDequeTail) -> do
-            writeIORef secondFetchedDequeRef secondFetchedDequeTail
-            return (just (firstFetched, secondFetched))
-          Nothing -> return nil
-
-instance Arrow Transform where
-  arr fn =
-    Transform (pure . fmap fn)
-  first =
-    first'
-
-instance ArrowChoice Transform where
-  left =
-    left'
-
-{-# INLINE consume #-}
-consume :: C.Consume input output -> Transform input output
-consume (C.Consume runFetch) =
-  Transform $ \ (A.Fetch fetch) -> do
-    stoppedRef <- newIORef False
-    return $ A.Fetch $ \ nil just -> do
-      stopped <- readIORef stoppedRef
-      if stopped
-        then return nil
-        else do
-          emittedRef <- newIORef False
-          output <-
-            runFetch $ A.Fetch $ \ inputNil inputJust ->
-            join
-              (fetch
-                (do
-                  writeIORef stoppedRef True
-                  return inputNil)
-                (\ !input -> do
-                  writeIORef emittedRef True
-                  return (inputJust input)))
-          stopped <- readIORef stoppedRef
-          if stopped
-            then do
-              emitted <- readIORef emittedRef
-              if emitted
-                then return (just output)
-                else return nil
-            else return (just output)
-
-{-# INLINABLE produce #-}
-produce :: (input -> D.Produce output) -> Transform input output
-produce inputToProduce =
-  Transform $ \ (A.Fetch inputFetchIO) -> do
-    stateRef <- newIORef Nothing
-    return $ A.Fetch $ \ nil just -> fix $ \ loop -> do
-      state <- readIORef stateRef
-      case state of
-        Just (A.Fetch outputFetchIO, kill) ->
-          join $ outputFetchIO
-            (kill >> writeIORef stateRef Nothing >> loop)
-            (return . just)
-        Nothing ->
-          join $ inputFetchIO (return nil) $ \ !input -> do
-            case inputToProduce input of
-              D.Produce produceIO -> do
-                fetchAndKill <- produceIO
-                writeIORef stateRef (Just fetchAndKill)
-                loop
-
-{-# INLINE mapFetch #-}
-mapFetch :: (A.Fetch a -> A.Fetch b) -> Transform a b
-mapFetch mapping =
-  Transform $ return . mapping
+module Potoki.Core.Transform
+(
+  Transform(..),
+  consume,
+  produce,
+  mapFetch,
+  -- * Basics
+  ioTransform,
+  take,
+  takeWhile,
+  drop,
+  mapFilter,
+  filter,
+  just,
+  list,
+  vector,
+  batch,
+  distinctBy,
+  distinct,
+  executeIO,
+  mapInIO,
+  reportProgress,
+  handleProgressAndCountOnInterval,
+  uniquify,
+  -- * ByteString
+  builderChunks,
+  extractLines,
+  extractLinesWithoutTrail,
+  extractLinesConcurrently,
+  -- * State
+  R.runState,
+  R.execState,
+  R.evalState,
+  -- * Parsing
+  B.scan,
+  A.parseBytes,
+  A.parseText,
+  A.parseLineBytesConcurrently,
+  A.parseNonEmptyLineBytesConcurrently,
+  -- * Concurrency
+  N.bufferize,
+  N.bufferizeFlushing,
+  N.concurrently,
+  N.concurrentlyInOrder,
+  N.unsafeConcurrently,
+  N.async,
+  N.concurrentlyWithBatching,
+  N.concurrentlyInOrderWithBatching,
+  -- * File IO
+  deleteFile,
+  appendBytesToFile,
+  writeTextToFile,
+  -- * Debugging
+  count,
+  mapInIOWithCounter,
+  handleCount,
+  handleCountOnInterval,
+  traceWithCounter,
+)
+where
 
-{-|
-Execute the IO action.
--}
-{-# INLINE executeIO #-}
-executeIO :: Transform (IO a) a
-executeIO =
-  mapFetch $ \ (A.Fetch fetchIO) -> A.Fetch $ \ nil just ->
-  join (fetchIO (return nil) (fmap just))
+import Potoki.Core.Types
+import Potoki.Core.Transform.Basic
+import Potoki.Core.Transform.FileIO
+import Potoki.Core.Transform.ByteString
+import qualified Potoki.Core.Transform.Attoparsec as A
+import qualified Potoki.Core.Transform.Scanner as B
+import qualified Potoki.Core.Transform.Concurrency as N
+import qualified Potoki.Core.Transform.State as R
 
-{-# INLINE take #-}
-take :: Int -> Transform input input
-take amount =
-  Transform $ \ (A.Fetch fetchIO) -> do
-    countRef <- newIORef amount
-    return $ A.Fetch $ \ nil just -> do
-      count <- readIORef countRef
-      if count > 0
-        then do
-          writeIORef countRef $! pred count
-          fetchIO nil just
-        else return nil
diff --git a/library/Potoki/Core/Transform/Attoparsec.hs b/library/Potoki/Core/Transform/Attoparsec.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/Attoparsec.hs
@@ -0,0 +1,107 @@
+module Potoki.Core.Transform.Attoparsec
+where
+
+import Potoki.Core.Prelude hiding (take, takeWhile, filter, drop)
+import Potoki.Core.Types
+import Potoki.Core.Transform.Basic
+import Potoki.Core.Transform.Concurrency
+import Potoki.Core.Transform.ByteString
+import qualified Potoki.Core.Fetch as A
+import qualified Data.Attoparsec.ByteString as K
+import qualified Data.Attoparsec.Text as L
+import qualified Data.Attoparsec.Types as M
+import qualified Acquire.Acquire as N
+import qualified Data.ByteString as ByteString
+
+
+{-# INLINE mapWithParseResult #-}
+mapWithParseResult :: forall input parsed. (Monoid input, Eq input) => (input -> M.IResult input parsed) -> Transform input (Either Text parsed)
+mapWithParseResult inputToResult =
+  Transform $ \inputFetch -> N.Acquire $ do
+    unconsumedRef <- newMutVar mempty
+    finishedRef <- newMutVar False
+    return (A.Fetch (fetchParsed inputFetch finishedRef unconsumedRef), return ())
+  where
+    fetchParsed :: A.Fetch input -> MutVar RealWorld Bool -> MutVar RealWorld input -> IO (Maybe (Either Text parsed))
+    fetchParsed (A.Fetch inputFetchIO) finishedRef unconsumedRef =
+      do
+        finished <- readMutVar finishedRef
+        if finished
+          then return Nothing
+          else do
+            unconsumed <- readMutVar unconsumedRef
+            if unconsumed == mempty
+              then
+                inputFetchIO >>= \case
+                  Nothing -> return Nothing
+                  Just input -> do
+                    if input == mempty
+                      then return Nothing
+                      else matchResult (inputToResult input)
+              else do
+                writeMutVar unconsumedRef mempty
+                matchResult (inputToResult unconsumed)
+      where
+        matchResult :: M.IResult input parsed -> IO (Maybe (Either Text parsed))
+        matchResult =
+          \case
+            M.Partial inputToResultVal ->
+              consumeVal inputToResultVal
+            M.Done unconsumed parsed ->
+              do
+                writeMutVar unconsumedRef unconsumed
+                return (Just (Right parsed))
+            M.Fail unconsumed contexts message ->
+              do
+                writeMutVar unconsumedRef unconsumed
+                writeMutVar finishedRef True
+                return (Just (Left resultMessage))
+              where
+                resultMessage =
+                  if null contexts
+                    then fromString message
+                    else fromString (showString (intercalate " > " contexts) (showString ": " message))
+        consumeVal inputToResultVal' =
+          inputFetchIO >>= \case
+            Nothing -> do
+              writeMutVar finishedRef True
+              matchResult (inputToResultVal' mempty)
+            Just input -> do
+              when (input == mempty) (writeMutVar finishedRef True)
+              matchResult (inputToResultVal' input)
+
+{-|
+Lift an Attoparsec ByteString parser.
+-}
+{-# INLINE parseBytes #-}
+parseBytes :: K.Parser parsed -> Transform ByteString (Either Text parsed)
+parseBytes parser =
+  mapWithParseResult (K.parse parser)
+
+{-|
+Lift an Attoparsec Text parser.
+-}
+{-# INLINE parseText #-}
+parseText :: L.Parser parsed -> Transform Text (Either Text parsed)
+parseText parser =
+  mapWithParseResult (L.parse parser)
+
+{-|
+Lift an Attoparsec ByteString parser to a transform,
+which parses the lines concurrently.
+-}
+{-# INLINE parseLineBytesConcurrently #-}
+parseLineBytesConcurrently :: NFData a => Int -> K.Parser a -> Transform ByteString (Either Text a)
+parseLineBytesConcurrently concurrency parser =
+  extractLines >>> bufferize concurrency >>>
+  unsafeConcurrently concurrency (arr (mapLeft fromString . K.parseOnly parser))
+
+{-|
+Lift an Attoparsec ByteString parser to a transform,
+which parses the lines concurrently.
+-}
+{-# INLINE parseNonEmptyLineBytesConcurrently #-}
+parseNonEmptyLineBytesConcurrently :: NFData a => Int -> K.Parser a -> Transform ByteString (Either Text a)
+parseNonEmptyLineBytesConcurrently concurrency parser =
+  extractLinesWithoutTrail >>> filter (not . ByteString.null) >>> bufferize (concurrency * 2) >>>
+  unsafeConcurrently concurrency (arr (mapLeft fromString . K.parseOnly parser))
diff --git a/library/Potoki/Core/Transform/Basic.hs b/library/Potoki/Core/Transform/Basic.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/Basic.hs
@@ -0,0 +1,353 @@
+module Potoki.Core.Transform.Basic
+where
+
+import Potoki.Core.Prelude hiding (take, takeWhile, filter, drop)
+import Potoki.Core.Types
+import qualified Potoki.Core.Fetch as A
+import qualified Data.HashSet as C
+import qualified Data.Vector as P
+import qualified Acquire.Acquire as M
+import qualified Data.Vector.Generic.Mutable as MutableGenericVector
+import qualified Data.Vector.Generic as GenericVector
+import qualified Text.Builder as TextBuilder
+import qualified Potoki.Core.TextBuilder as TextBuilder
+
+
+{-# INLINE mapFilter #-}
+mapFilter :: (input -> Maybe output) -> Transform input output
+mapFilter mapping =
+  Transform (return . A.mapFilter mapping)
+
+{-# INLINE filter #-}
+filter :: (input -> Bool) -> Transform input input
+filter predicate =
+  Transform (pure . A.filter predicate)
+
+{-# INLINE just #-}
+just :: Transform (Maybe input) input
+just =
+  Transform (pure . A.just)
+
+{-# INLINE takeWhile #-}
+takeWhile :: (input -> Bool) -> Transform input input
+takeWhile predicate =
+  Transform (pure . A.takeWhile predicate)
+
+{-# INLINE drop #-}
+drop :: Int -> Transform input input
+drop amount =
+  Transform $ \ (A.Fetch fetchIO) -> do
+    countRef <- liftIO $ newIORef amount
+    return $ Fetch $ do
+      let
+        loop = do
+          count <- readIORef countRef
+          fetch <- fetchIO
+          case fetch of
+            Just _ ->
+              if count > 0
+                then do
+                  writeIORef countRef $! pred count
+                  loop
+                else (return fetch)
+            Nothing -> return Nothing
+      loop
+
+{-# INLINE list #-}
+list :: Transform [a] a
+list =
+  Transform $ \ (A.Fetch fetchListIO) -> liftIO $ do
+    bufferRef <- newIORef []
+    let
+      fetchFromBufferOr whenEmpty = do
+        buffer <- readIORef bufferRef
+        case buffer of
+          (!head) : tail -> do
+            writeIORef bufferRef tail
+            return (Just head)
+          _ -> whenEmpty
+      fetchFromSource = do
+        fetchedList <- fetchListIO
+        case fetchedList of
+          Just list -> case list of
+            (!head) : tail -> do
+              writeIORef bufferRef tail
+              return (Just head)
+            _ -> do
+              fetchFromSource
+          Nothing -> return Nothing
+      in return $ A.Fetch $ fetchFromBufferOr fetchFromSource
+
+{-# INLINABLE vector #-}
+vector :: GenericVector.Vector vector a => Transform (vector a) a
+vector =
+  Transform $ \ (Fetch fetchVectorIO) -> liftIO $ do
+    indexRef <- newIORef 0
+    vectorRef <- newIORef GenericVector.empty
+    return $ Fetch $ let
+      loop = do
+        vectorVal <- readIORef vectorRef
+        indexVal <- readIORef indexRef
+        if indexVal < GenericVector.length vectorVal
+          then do
+            writeIORef indexRef (succ indexVal)
+            return (Just (GenericVector.unsafeIndex vectorVal indexVal))
+          else fetchVectorIO >>= \case 
+            Just vectorVal' -> do
+              writeIORef vectorRef vectorVal'
+              writeIORef indexRef 0
+              loop
+            Nothing -> return Nothing
+      in loop
+
+{-|
+Chunk the stream to vector batches of the given size.
+
+It's useful in combination with 'concurrently' in cases where the lifted transform's iteration is too light.
+Actually, there is a composed variation of 'concurrently', which utilizes it: 'concurrentlyWithBatching'.
+-}
+{-# INLINABLE batch #-}
+batch :: GenericVector.Vector vector a => Int -> Transform a (vector a)
+batch size = if size < 1
+  then Transform $ const $ liftIO $ return $ empty
+  else Transform $ \ (Fetch fetch) -> liftIO $ do
+    mvec <- MutableGenericVector.new size
+    cursor <- newIORef 0
+    activeVar <- newIORef True
+    return $ Fetch $ let
+      loop = do
+        active <- readIORef activeVar
+        if active
+          then do
+            fetchingResult <- fetch
+            case fetchingResult of
+              Just !a -> do
+                index <- readIORef cursor
+                MutableGenericVector.unsafeWrite mvec index a
+                let !nextIndex = succ index
+                if nextIndex == size
+                  then do
+                    writeIORef cursor 0
+                    !vec <- GenericVector.freeze mvec
+                    return (Just vec)
+                  else do
+                    writeIORef cursor nextIndex
+                    loop
+              Nothing -> do
+                writeIORef activeVar False
+                index <- readIORef cursor
+                if index > 0
+                  then do
+                    !vec <- GenericVector.freeze (MutableGenericVector.unsafeSlice 0 index mvec)
+                    return (Just vec)
+                  else return Nothing
+          else return Nothing
+      in loop
+
+{-# INLINE distinctBy #-}
+distinctBy :: (Eq comparable, Hashable comparable) => (element -> comparable) -> Transform element element
+distinctBy f =
+  Transform $ \ (A.Fetch fetch) -> M.Acquire $ do
+    stateRef <- newIORef mempty
+    return $ (, return ()) $ A.Fetch $ let
+      loop = fetch >>= \case
+        Nothing -> return Nothing
+        Just input -> do
+          let comparable = f input
+          !set <- readIORef stateRef
+          if C.member comparable set
+            then loop
+            else do
+              writeIORef stateRef $! C.insert comparable set
+              return (Just input)
+      in loop
+
+{-# INLINE distinct #-}
+distinct :: (Eq element, Hashable element) => Transform element element
+distinct = distinctBy id
+
+{-# INLINE mapInIO #-}
+mapInIO :: (a -> IO b) -> Transform a b
+mapInIO io =
+  Transform $ \ (A.Fetch fetch) -> M.Acquire $ 
+  return $ (, return ()) $ A.Fetch $ 
+  join $ (sequence . fmap io) <$> fetch
+
+{-# INLINE ioTransform #-}
+ioTransform :: IO (Transform a b) -> Transform a b
+ioTransform io =
+  Transform $ \ fetch -> do
+    Transform acquire <- liftIO io
+    acquire fetch
+
+count :: Transform a Int
+count = Transform $ \ (Fetch fetchIO) -> do
+  counter <- liftIO (newIORef 0)
+  return $ Fetch $ do
+    result <- fetchIO
+    case result of
+      Just _ -> Just <$> atomicModifyIORef' counter (\ n -> (succ n, n))
+      Nothing -> return Nothing
+
+mapInIOWithCounter :: (Int -> a -> IO b) -> Transform a b
+mapInIOWithCounter handler =
+  ioTransform $ do
+    counter <- newIORef 0
+    return $ mapInIO $ \ !a -> do
+      count <- atomicModifyIORef' counter (\ n -> (succ n, n))
+      handler count a
+
+handleCount :: (Int -> IO ()) -> Transform a a
+handleCount handler = mapInIOWithCounter $ \ count a -> do
+  handler count
+  return a
+
+{-|
+Provides for progress monitoring by means of periodic measurement.
+-}
+handleCountOnInterval :: NominalDiffTime -> (Int -> IO ()) -> Transform a a
+handleCountOnInterval interval handler = ioTransform $ do
+  nextTime <- addUTCTime interval <$> getCurrentTime
+  nextTimeRef <- newIORef nextTime
+  return $ handleCount $ \ count -> do
+    nextTime <- readIORef nextTimeRef
+    time <- getCurrentTime
+    when (time >= nextTime) $ do
+      writeIORef nextTimeRef (addUTCTime interval nextTime)
+      handler count
+
+{-|
+Useful for debugging
+-}
+traceWithCounter :: (Int -> String) -> Transform a a
+traceWithCounter shower = handleCount (putStrLn . shower)
+
+{-# INLINE consume #-}
+consume :: Consume input output -> Transform input output
+consume (Consume runFetch) =
+  Transform $ \ (Fetch inputIO) -> do
+    stoppedRef <- liftIO $ newIORef False
+    return $ Fetch $ do
+      stopped <- readIORef stoppedRef
+      if stopped
+        then do
+          writeIORef stoppedRef False
+          return Nothing
+        else do
+          emittedRef <- newIORef False
+          output <- runFetch $ Fetch $ do
+            input <- inputIO
+            case input of
+              Nothing     -> do
+                writeIORef stoppedRef True
+                return Nothing
+              Just element -> do
+                writeIORef emittedRef True
+                return $ Just element
+          checkStopped <- readIORef stoppedRef
+          if checkStopped
+            then do
+              emitted <- readIORef emittedRef
+              if emitted
+                then return $ Just output
+                else do
+                  writeIORef stoppedRef False
+                  return Nothing
+            else return $ Just output
+
+{-# INLINABLE produce #-}
+produce :: (input -> Produce output) -> Transform input output
+produce inputToProduce =
+  Transform $ \ (Fetch inputFetchIO) -> do
+    stateRef <- liftIO $ newIORef Nothing
+    return $ Fetch $ let
+      loop = do
+        state <- readIORef stateRef
+        case state of
+          Just (Fetch outputFetchIO, kill) ->
+            do
+              outputFetchResult <- outputFetchIO
+              case outputFetchResult of
+                Just x -> return (Just x)
+                Nothing -> do
+                  kill
+                  writeIORef stateRef Nothing
+                  loop
+          Nothing ->
+            do
+              inputFetchResult <- inputFetchIO
+              case inputFetchResult of
+                Just input -> do
+                  case inputToProduce input of
+                    Produce (Acquire produceIO) -> do
+                      fetchAndKill <- produceIO
+                      writeIORef stateRef (Just fetchAndKill)
+                      loop
+                Nothing -> return Nothing
+      in loop
+
+{-# INLINE mapFetch #-}
+mapFetch :: (Fetch a -> Fetch b) -> Transform a b
+mapFetch mapping =
+  Transform $ return . mapping
+
+{-|
+Execute the IO action.
+-}
+{-# INLINE executeIO #-}
+executeIO :: Transform (IO a) a
+executeIO =
+  mapFetch $ \ (Fetch fetchIO) -> Fetch (fetchIO >>= sequence)
+
+{-# INLINE take #-}
+take :: Int -> Transform input input
+take amount
+  | amount <= 0 =
+    Transform $ \ _ -> return $ Fetch $ return Nothing
+  | otherwise   =
+    Transform $ \ (Fetch fetchIO) -> do
+      countRef <- liftIO $ newIORef amount
+      return $ Fetch $ do
+        count <- readIORef countRef
+        if count > 0
+          then do
+            modifyIORef countRef pred
+            fetchIO
+          else
+            return Nothing
+
+reportProgress :: (Text -> IO ()) -> Transform a a
+reportProgress log = handleProgressAndCountOnInterval 1 $ \ progress count ->
+  log $ TextBuilder.run $ TextBuilder.streamProgressMessage progress count
+
+handleProgressAndCountOnInterval :: NominalDiffTime -> (Int -> Int -> IO ()) -> Transform a a
+handleProgressAndCountOnInterval interval handle = ioTransform $ do
+  lastCountRef <- newIORef 0
+  return $ handleCountOnInterval interval $ \ count -> do
+    lastCount <- readIORef lastCountRef
+    writeIORef lastCountRef count
+    handle (count - lastCount) count
+
+uniquify :: (Eq a) => Transform a a
+uniquify = 
+  Transform $ \ (Fetch fetchIO) -> M.Acquire $ do
+    duplicateRef <- newIORef Nothing
+    return $ (, return ()) $ A.Fetch $ let
+      loop = do
+        dupliacte <- readIORef duplicateRef
+        fetch <- fetchIO
+        case fetch of
+          Nothing -> return Nothing
+          Just elem -> do
+            case dupliacte of
+              Nothing -> do
+                writeIORef duplicateRef fetch
+                return fetch
+              Just dupl -> do
+                if elem == dupl 
+                  then
+                    loop
+                  else do
+                    writeIORef duplicateRef (Just elem)
+                    return fetch
+      in loop
diff --git a/library/Potoki/Core/Transform/ByteString.hs b/library/Potoki/Core/Transform/ByteString.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/ByteString.hs
@@ -0,0 +1,139 @@
+module Potoki.Core.Transform.ByteString
+where
+
+import Potoki.Core.Prelude hiding (filter)
+import Potoki.Core.Transform.Basic
+import Potoki.Core.Transform.Instances ()
+import Potoki.Core.Transform.Concurrency
+import Potoki.Core.Types
+import qualified Potoki.Core.Fetch as A
+import qualified Potoki.Core.Produce as H
+import qualified Ptr.Poking as C
+import qualified Ptr.ByteString as D
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Builder as E
+import qualified Data.ByteString.Lazy as F
+import qualified Acquire.Acquire as M
+
+
+{-# INLINE builderChunks #-}
+builderChunks :: Transform E.Builder ByteString
+builderChunks =
+  produce (H.list . F.toChunks . E.toLazyByteString)
+
+{-|
+Convert freeform bytestring chunks into chunks,
+which are strictly separated by newline no matter how long they may be.
+-}
+extractLines :: Transform ByteString ByteString
+extractLines =
+  lineList >>> list
+  where
+    lineList =
+      Transform $ \ (Fetch fetchIO) -> liftIO $ do
+        stateRef <- newIORef Nothing
+        return $ Fetch $ fetchIO >>= \case
+          Just chunk ->
+            case B.split 10 chunk of
+              firstInput : tailVal -> do
+                state <- readIORef stateRef
+                let
+                  newPoking =
+                    fold state <> C.bytes firstInput
+                  in case unsnoc tailVal of
+                    Just (!initVal, !lastVal) ->
+                      do
+                        writeIORef stateRef $! Just $! C.bytes lastVal
+                        let !bytes = D.poking newPoking
+                        return (Just (bytes : initVal))
+                    Nothing ->
+                      do
+                        writeIORef stateRef (Just newPoking)
+                        return (Just [])
+              _ -> return (Just [])
+          Nothing -> do
+            state <- readIORef stateRef
+            case state of
+              Just poking -> do
+                writeIORef stateRef Nothing
+                return (Just [D.poking poking])
+              Nothing -> return Nothing
+
+extractLinesWithoutTrail :: Transform ByteString ByteString
+extractLinesWithoutTrail =
+  lineList >>> list
+  where
+    lineList =
+      Transform $ \ (Fetch fetchIO) -> liftIO $ do
+        pokingRef <- newIORef mempty
+        return $ Fetch $ do
+          fetchResult <- fetchIO
+          case fetchResult of
+            Just chunk -> case B.split 10 chunk of
+              head : tail -> do
+                poking <- readIORef pokingRef
+                let newPoking = poking <> C.bytes head
+                case unsnoc tail of
+                  Just (!init, last) -> do
+                    writeIORef pokingRef (C.bytes last)
+                    let
+                      !bytes = D.poking newPoking
+                      in return (Just (bytes : init))
+                  Nothing -> do
+                    writeIORef pokingRef newPoking
+                    return (Just [])
+              _ -> return (Just [])
+            Nothing -> do
+              poking <- readIORef pokingRef
+              if C.null poking
+                then return Nothing
+                else let
+                  !bytes = D.poking poking
+                  in do
+                    writeIORef pokingRef mempty
+                    return (Just [bytes])
+
+extractLinesConcurrently :: Int -> Transform ByteString ByteString
+extractLinesConcurrently concurrency =
+  concurrentlyInOrderWithBatching 100 concurrency (arr (B.split 10)) >>> mergeLineChunks
+
+mergeLineChunks :: Transform [ByteString] ByteString
+mergeLineChunks = Transform $ \ (Fetch fetchChunkList) -> liftIO $ do
+  cachedChunkListRef <- newIORef (Just [])
+  incompleteChunkRef <- newIORef Nothing
+  return $ Fetch $ let
+    loop = do
+      cachedChunkListIfAny <- readIORef cachedChunkListRef
+      case cachedChunkListIfAny of
+        Just cachedChunkList -> case cachedChunkList of
+          (!a) : b : tail -> do
+            writeIORef cachedChunkListRef (Just (b : tail))
+            return (Just a)
+          (!a) : _ -> do
+            writeIORef cachedChunkListRef (Just [])
+            writeIORef incompleteChunkRef (Just a)
+            loop
+          _ -> do
+            newChunkListIfAny <- fetchChunkList
+            incompleteChunkIfAny <- readIORef incompleteChunkRef
+            case incompleteChunkIfAny of
+              Just incompleteChunk -> case newChunkListIfAny of
+                Just newChunkList -> case newChunkList of
+                  a : b : tail -> do
+                    completeChunk <- evaluate (incompleteChunk <> a)
+                    writeIORef cachedChunkListRef (Just (b : tail))
+                    writeIORef incompleteChunkRef Nothing
+                    return (Just completeChunk)
+                  a : _ -> do
+                    newIncompleteChunk <- evaluate (incompleteChunk <> a)
+                    writeIORef incompleteChunkRef (Just newIncompleteChunk)
+                    loop
+                  _ -> loop
+                Nothing -> do
+                  writeIORef cachedChunkListRef Nothing
+                  return (Just incompleteChunk)
+              Nothing -> do
+                writeIORef cachedChunkListRef newChunkListIfAny
+                loop
+        Nothing -> return Nothing
+    in loop
diff --git a/library/Potoki/Core/Transform/Concurrency.hs b/library/Potoki/Core/Transform/Concurrency.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/Concurrency.hs
@@ -0,0 +1,227 @@
+module Potoki.Core.Transform.Concurrency
+where
+
+import Potoki.Core.Prelude hiding (take, takeWhile, filter)
+import Potoki.Core.Transform.Instances ()
+import Potoki.Core.Transform.Basic
+import Potoki.Core.Types
+import qualified Potoki.Core.Fetch as A
+import qualified Acquire.Acquire as M
+
+
+bufferizeFlushing :: Int -> Transform input [input]
+bufferizeFlushing maxSize =
+  Transform $ \ (A.Fetch fetchIO) -> liftIO $ do
+    buffer <- newTBQueueIO (fromIntegral maxSize)
+    activeVar <- newTVarIO True
+
+    forkIO $ let
+      loop = do
+        fetchingResult <- fetchIO
+        case fetchingResult of
+          Just !element -> do
+            atomically $ writeTBQueue buffer element
+            loop
+          Nothing -> atomically $ writeTVar activeVar False
+      in loop
+
+    return $ Fetch $ atomically $ do
+      batch <- flushTBQueue buffer
+      if null batch
+        then do
+          active <- readTVar activeVar
+          if active
+            then retry
+            else return Nothing
+        else return (Just batch)
+
+{-# INLINE bufferize #-}
+bufferize :: NFData element => Int -> Transform element element
+bufferize size =
+  Transform $ \ (A.Fetch fetchIO) -> liftIO $ do
+    buffer <- newTBQueueIO (fromIntegral size)
+    activeVar <- newTVarIO True
+
+    forkIO $ let
+      loop = do
+        fetchingResult <- fetchIO
+        case fetchingResult of
+          Just element -> do
+            forcedElement <- evaluate (force element)
+            atomically $ writeTBQueue buffer forcedElement
+            loop
+          Nothing -> atomically $ writeTVar activeVar False
+      in loop
+
+    return $ Fetch $ let
+      readBuffer = Just <$> readTBQueue buffer
+      terminate = do
+        active <- readTVar activeVar
+        if active
+          then empty
+          else return Nothing
+      in atomically (readBuffer <|> terminate)
+
+{-|
+Identity Transform, which ensures that the inputs are fetched synchronously.
+
+Useful for concurrent transforms.
+-}
+{-# INLINABLE sync #-}
+sync :: Transform a a
+sync =
+  Transform $ \ (A.Fetch fetchIO) -> liftIO $ do
+    activeVar <- newMVar True
+    return $ A.Fetch $ do
+      active <- takeMVar activeVar
+      if active
+        then fetchIO >>= \ case
+          Just !element -> do
+            putMVar activeVar True
+            return (Just element)
+          Nothing -> do
+            putMVar activeVar False
+            return Nothing
+        else do
+          putMVar activeVar False
+          return Nothing
+
+{-|
+Execute the transform on the specified amount of threads.
+The order of the outputs produced is indiscriminate.
+-}
+{-# INLINABLE concurrently #-}
+concurrently :: NFData output => Int -> Transform input output -> Transform input output
+concurrently workersAmount transform =
+  if workersAmount == 1
+    then transform
+    else
+      sync >>>
+      unsafeConcurrently workersAmount transform
+
+{-# INLINE unsafeConcurrently #-}
+unsafeConcurrently :: NFData output => Int -> Transform input output -> Transform input output
+unsafeConcurrently workersAmount (Transform syncTransformIO) = 
+  Transform $ \ fetchIO -> liftIO $ do
+    chan <- newTBQueueIO (fromIntegral (workersAmount * 2))
+    workersCounter <- newTVarIO workersAmount
+
+    replicateM_ workersAmount $ forkIO $ do
+      (A.Fetch fetchIO, finalize) <- case syncTransformIO fetchIO of M.Acquire io -> io
+      let
+        loop = do
+          fetchResult <- fetchIO
+          case fetchResult of
+            Just result -> do
+              forcedResult <- evaluate (force result)
+              atomically (writeTBQueue chan forcedResult)
+              loop
+            Nothing -> atomically (modifyTVar' workersCounter pred)
+        in loop *> finalize
+
+    return $ A.Fetch $ let
+      readChan = Just <$> readTBQueue chan
+      terminate = do
+        workersActive <- readTVar workersCounter
+        if workersActive > 0
+          then empty
+          else return Nothing
+      in atomically (readChan <|> terminate)
+
+concurrentlyInOrder :: NFData b => Int -> Transform a b -> Transform a b
+concurrentlyInOrder concurrency (Transform transform) = Transform $ \ (Fetch fetchA) -> liftIO $ do
+  inputQueue <- newTBQueueIO (fromIntegral concurrency)
+  outputSlotQueue <- newTQueueIO
+  liveWorkersVar <- newTVarIO concurrency
+
+  forkIO $ let
+    loop = do
+      fetchAResult <- fetchA
+      case fetchAResult of
+        Just a -> do
+          atomically $ writeTBQueue inputQueue (Just a)
+          loop
+        Nothing -> atomically $ replicateM_ concurrency $ writeTBQueue inputQueue Nothing
+    in loop
+
+  replicateM_ concurrency $ forkIO $ do
+    outputQueue <- newTQueueIO
+    needsSwitchVar <- newTVarIO False
+
+    let
+      localizedFetchA = Fetch $ atomically $ do
+        needsSwitch <- readTVar needsSwitchVar
+        if needsSwitch
+          then writeTQueue outputQueue Nothing
+          else writeTVar needsSwitchVar True
+        writeTQueue outputSlotQueue outputQueue
+        readTBQueue inputQueue
+
+      in do
+        (Fetch fetchB, finalize) <- case transform localizedFetchA of M.Acquire io -> io
+        let
+          loop = do
+            fetchBResult <- fetchB
+            case fetchBResult of
+              Just b -> do
+                forcedB <- evaluate (force b)
+                atomically $ writeTQueue outputQueue (Just forcedB)
+                loop
+              Nothing -> do
+                atomically $ do
+                  writeTQueue outputQueue Nothing
+                  modifyTVar' liveWorkersVar pred
+                finalize
+            in loop
+
+  return $ Fetch $ atomically $ fix $ \ loop -> mplus
+    (do
+      outputQueue <- peekTQueue outputSlotQueue
+      bIfAny <- readTQueue outputQueue
+      case bIfAny of
+        Just b -> return (Just b)
+        Nothing -> do
+          readTQueue outputSlotQueue
+          loop)
+    (do
+      liveWorkers <- readTVar liveWorkersVar
+      guard (liveWorkers <= 0)
+      return Nothing)
+
+{-|
+A transform, which fetches the inputs asynchronously on the specified number of threads.
+-}
+async :: NFData input => Int -> Transform input input
+async workersAmount = 
+  Transform $ \ (A.Fetch fetchIO) -> liftIO $ do
+    chan <- atomically newEmptyTMVar
+    workersCounter <- atomically (newTVar workersAmount)
+
+    replicateM_ workersAmount $ forkIO $ let
+      loop = do
+        fetchResult <- fetchIO
+        case fetchResult of
+          Just input -> atomically (putTMVar chan input) *> loop
+          Nothing -> atomically (modifyTVar' workersCounter pred)
+      in loop
+
+    return $ A.Fetch $ let
+      readChan = Just <$> takeTMVar chan
+      terminate = do
+        workersActive <- readTVar workersCounter
+        if workersActive > 0
+          then empty
+          else return Nothing
+      in atomically (readChan <|> terminate)
+
+concurrentlyWithBatching :: (NFData a, NFData b) => Int -> Int -> Transform a b -> Transform a b
+concurrentlyWithBatching batching concurrency transform =
+  batch @Vector batching >>> bufferize concurrency >>>
+  unsafeConcurrently concurrency (vector >>> transform >>> batch @Vector batching) >>>
+  vector
+
+concurrentlyInOrderWithBatching :: (NFData b) => Int -> Int -> Transform a b -> Transform a b
+concurrentlyInOrderWithBatching batching concurrency transform =
+  batch @Vector batching >>> 
+  concurrentlyInOrder concurrency (vector >>> transform >>> batch @Vector batching) >>>
+  vector
diff --git a/library/Potoki/Core/Transform/FileIO.hs b/library/Potoki/Core/Transform/FileIO.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/FileIO.hs
@@ -0,0 +1,30 @@
+module Potoki.Core.Transform.FileIO
+where
+
+import Potoki.Core.Prelude hiding (take, takeWhile, filter, drop)
+import Potoki.Core.Transform.Basic
+import Potoki.Core.Types
+import qualified Data.ByteString as J
+import qualified Data.Text.IO as Q
+import qualified System.Directory as I
+
+
+{-# INLINE deleteFile #-}
+deleteFile :: Transform FilePath (Either IOException ())
+deleteFile =
+  mapInIO (try . I.removeFile)
+
+{-# INLINE appendBytesToFile #-}
+appendBytesToFile :: Transform (FilePath, ByteString) (Either IOException ())
+appendBytesToFile =
+  mapInIO $ \ (path, bytes) ->
+  try $ 
+  withFile path AppendMode $ \ handleVal -> 
+  J.hPut handleVal bytes
+
+{-# INLINABLE writeTextToFile #-}
+writeTextToFile :: Transform (FilePath, Text) (Either IOException ())
+writeTextToFile =
+  mapInIO $ \ (path, text) ->
+  try $ 
+  Q.writeFile path text
diff --git a/library/Potoki/Core/Transform/Instances.hs b/library/Potoki/Core/Transform/Instances.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/Instances.hs
@@ -0,0 +1,53 @@
+module Potoki.Core.Transform.Instances where
+
+import Potoki.Core.Prelude
+import Potoki.Core.Types
+import qualified Potoki.Core.Fetch as A
+
+instance Category Transform where
+  id =
+    Transform (return)
+  (.) (Transform leftVal) (Transform rightVal) =
+    Transform (leftVal <=< rightVal)
+
+instance Profunctor Transform where
+  dimap inputMapping outputMapping (Transform acquire) =
+    Transform $ \ oldFetch -> do
+      newFetch <- acquire (fmap inputMapping oldFetch)
+      return $ fmap outputMapping newFetch
+
+instance Choice Transform where
+  right' :: Transform a b -> Transform (Either c a) (Either c b)
+  right' (Transform rightTransformAcquire) =
+    Transform $ \ inFetch -> do
+      fetchedLeftMaybeRef <- liftIO $ newIORef Nothing
+      Fetch rightFetchIO <- rightTransformAcquire (A.rightHandlingLeft (writeIORef fetchedLeftMaybeRef . Just) inFetch)
+      return $ Fetch $ do
+        rightFetch <- rightFetchIO
+        case rightFetch of
+          Nothing -> do
+            fetchedLeftMaybe <- readIORef fetchedLeftMaybeRef
+            case fetchedLeftMaybe of
+              Nothing -> return Nothing
+              Just fetchedLeft -> do
+                writeIORef fetchedLeftMaybeRef Nothing
+                return $ Just (Left fetchedLeft)
+          Just element -> return $ Just (Right element)
+
+instance Strong Transform where
+  first' (Transform firstTransformAcquire) =
+    Transform $ \ inFetch -> do
+      cacheRef <- liftIO $ newIORef undefined
+      outFetch <- firstTransformAcquire (A.firstCachingSecond cacheRef inFetch)
+      return $ A.bothFetchingFirst cacheRef outFetch
+
+instance Arrow Transform where
+  arr fn =
+    Transform (return . fmap fn)
+  first =
+    first'
+
+instance ArrowChoice Transform where
+  left =
+    left'
+
diff --git a/library/Potoki/Core/Transform/Scanner.hs b/library/Potoki/Core/Transform/Scanner.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/Scanner.hs
@@ -0,0 +1,54 @@
+module Potoki.Core.Transform.Scanner
+where
+
+import Potoki.Core.Prelude hiding (take, takeWhile, filter, drop)
+import Potoki.Core.Types
+import Potoki.Core.Transform.Basic
+import Scanner
+
+
+{-# INLINE scan #-}
+scan :: Scanner a -> Transform ByteString (Either Text a)
+scan scanner =
+  Transform $ \ (Fetch fetchIo) -> liftIO $ do
+    unconsumedRef <- newMutVar mempty
+    finishedRef <- newMutVar False
+    return $ Fetch $ let
+      matchResult =
+        \ case
+          More inputToResultVal ->
+            consumeVal inputToResultVal
+          Done unconsumed parsed ->
+            do
+              writeMutVar unconsumedRef unconsumed
+              return (Just (Right parsed))
+          Fail unconsumed message ->
+            do
+              writeMutVar unconsumedRef unconsumed
+              writeMutVar finishedRef True
+              return (Just (Left (fromString message)))
+      consumeVal inputToResultVal' =
+        fetchIo >>= \ case
+          Nothing -> do
+            writeMutVar finishedRef True
+            matchResult (inputToResultVal' mempty)
+          Just input -> do
+            when (input == mempty) (writeMutVar finishedRef True)
+            matchResult (inputToResultVal' input)
+      in do
+          finished <- readMutVar finishedRef
+          if finished
+            then return Nothing
+            else do
+              unconsumed <- readMutVar unconsumedRef
+              if unconsumed == mempty
+                then
+                  fetchIo >>= \ case
+                    Nothing -> return Nothing
+                    Just input -> do
+                      if input == mempty
+                        then return Nothing
+                        else matchResult (Scanner.scan scanner input)
+                else do
+                  writeMutVar unconsumedRef mempty
+                  matchResult (Scanner.scan scanner unconsumed)
diff --git a/library/Potoki/Core/Transform/State.hs b/library/Potoki/Core/Transform/State.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Transform/State.hs
@@ -0,0 +1,40 @@
+module Potoki.Core.Transform.State
+where
+
+import Potoki.Core.Prelude
+import Potoki.Core.Types
+import Potoki.Core.Transform.Instances ()
+import qualified Potoki.Core.Fetch as A
+import qualified Control.Monad.Trans.State.Strict as O
+import qualified Acquire.Acquire as M
+
+
+{-|
+Notice that you can control the emission of output of each step
+by producing a list of outputs and then composing the transform with
+the "list" transform.
+-}
+{-# INLINE runState #-}
+runState :: (input -> O.State state output) -> state -> Transform input (output, state)
+runState stateFn initialState =
+  Transform $ \ (A.Fetch fetchIO) -> M.Acquire $ do
+    stateRef <- newIORef initialState
+    return $ (, return ()) $ A.Fetch $  
+      fetchIO >>= \case
+        Just input -> do
+          currentState <- readIORef stateRef
+          case O.runState (stateFn input) currentState of
+            (output, newState) -> do
+              writeIORef stateRef newState
+              return (Just (output, newState))
+        Nothing -> return Nothing
+
+{-# INLINE evalState #-}
+evalState :: (input -> O.State state output) -> state -> Transform input output
+evalState stateFn initialState =
+  rmap fst (runState stateFn initialState)
+
+{-# INLINE execState #-}
+execState :: (input -> O.State state output) -> state -> Transform input state
+execState stateFn initialState =
+  rmap snd (runState stateFn initialState)
diff --git a/library/Potoki/Core/Types.hs b/library/Potoki/Core/Types.hs
new file mode 100644
--- /dev/null
+++ b/library/Potoki/Core/Types.hs
@@ -0,0 +1,34 @@
+module Potoki.Core.Types
+where
+
+import Potoki.Core.Prelude
+
+
+{-|
+Passive producer of elements.
+-}
+newtype Fetch element =
+  Fetch (IO (Maybe element))
+
+{-|
+Passive producer of elements with support for early termination
+and resource management.
+-}
+newtype Produce element =
+  Produce (Acquire (Fetch element))
+
+{-|
+Active consumer of input into output.
+Sort of like a reducer in Map/Reduce.
+
+Automates the management of resources.
+-}
+newtype Consume input output =
+  {-|
+  An action, which executes the provided fetch in IO,
+  while managing the resources behind the scenes.
+  -}
+  Consume (Fetch input -> IO output)
+
+newtype Transform input output =
+  Transform (Fetch input -> Acquire (Fetch output))
diff --git a/potoki-core.cabal b/potoki-core.cabal
--- a/potoki-core.cabal
+++ b/potoki-core.cabal
@@ -1,47 +1,31 @@
-name:
-  potoki-core
-version:
-  1.2
-synopsis:
-  Low-level components of "potoki"
+name: potoki-core
+version: 2.3.4.1
+synopsis: Low-level components of "potoki"
 description:
   Provides everything required for building custom instances of
   the \"potoki\" abstractions.
   Consider this library to be the Internals modules of \"potoki\".
-category:
-  Streaming
-homepage:
-  https://github.com/nikita-volkov/potoki-core 
-bug-reports:
-  https://github.com/nikita-volkov/potoki-core/issues 
-author:
-  Nikita Volkov <nikita.y.volkov@mail.ru>
-maintainer:
-  Nikita Volkov <nikita.y.volkov@mail.ru>
-copyright:
-  (c) 2017, Nikita Volkov
-license:
-  MIT
-license-file:
-  LICENSE
-build-type:
-  Simple
-cabal-version:
-  >=1.10
+category: Streaming
+homepage: https://github.com/metrix-ai/potoki-core
+bug-reports: https://github.com/metrix-ai/potoki-core/issues
+author: Nikita Volkov <nikita.y.volkov@mail.ru>
+maintainer: Metrix.AI Tech Team <tech@metrix.ai>
+copyright: (c) 2017, Metrix.AI
+license: MIT
+license-file: LICENSE
+build-type: Simple
+cabal-version: >=1.10
+tested-with: GHC ==8.4.2, GHC ==8.6.*
 
+
 source-repository head
-  type:
-    git
-  location:
-    git://github.com/nikita-volkov/potoki-core.git
+  type: git
+  location: git://github.com/metrix-ai/potoki-core.git
 
 library
-  hs-source-dirs:
-    library
-  default-extensions:
-    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
-  default-language:
-    Haskell2010
+  hs-source-dirs: library
+  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language: Haskell2010
   exposed-modules:
     Potoki.Core.Produce
     Potoki.Core.Fetch
@@ -49,35 +33,75 @@
     Potoki.Core.IO
     Potoki.Core.Transform
   other-modules:
+    Potoki.Core.TextBuilder
+    Potoki.Core.Types
     Potoki.Core.Prelude
+    Potoki.Core.Transform.Attoparsec
+    Potoki.Core.Transform.Basic
+    Potoki.Core.Transform.ByteString
+    Potoki.Core.Transform.Concurrency
+    Potoki.Core.Transform.FileIO
+    Potoki.Core.Transform.State
+    Potoki.Core.Transform.Scanner
+    Potoki.Core.Transform.Instances
+    Potoki.Core.IO.Fetch
   build-depends:
-    -- 
-    deque >=0.2 && <0.3,
-    -- 
-    stm >=2.4 && <3,
-    -- 
+    acquire >=0.2 && <0.3,
+    attoparsec >=0.13 && <0.15,
+    base >=4.9 && <5,
+    bytestring ==0.10.*,
+    deepseq >=1.4 && <2,
+    deferred-folds >=0.9.7.1 && <0.10,
+    directory >=1.3 && <2,
+    foldl >=1.3 && <2,
+    hashable >=1 && <2,
+    primitive >=0.6.4 && <0.7,
     profunctors >=5.2 && <6,
-    base >=4.7 && <5
+    ptr >=0.16.2 && <0.17,
+    scanner >=0.3 && <0.4,
+    stm >=2.5 && <2.6,
+    stm-chans >=3 && <3.1,
+    text >=1 && <2,
+    text-builder >=0.6.3 && <0.7,
+    time >=1.5 && <2,
+    transformers >=0.5 && <0.6,
+    unordered-containers >=0.2 && <0.3,
+    vector >=0.12 && <0.13
 
-test-suite tests
-  type:
-    exitcode-stdio-1.0
-  hs-source-dirs:
-    tests
-  main-is:
-    Main.hs
-  default-extensions:
-    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
-  default-language:
-    Haskell2010
+test-suite test
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Main.hs
+  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language: Haskell2010
+  ghc-options: -threaded "-with-rtsopts=-N"
+  other-modules:
+    Potoki
+    Transform
   build-depends:
-    -- 
+    acquire >=0.2 && <0.3,
+    attoparsec,
+    deferred-folds,
+    foldl >=1.3.7 && <2,
+    ilist >=0.3.1.0 && <0.4,
+    split >=0.2.3.3 && <0.3,
     potoki-core,
-    -- testing:
-    tasty >=0.12 && <0.13,
-    tasty-quickcheck >=0.9 && <0.10,
-    tasty-hunit >=0.9 && <0.10,
-    quickcheck-instances >=0.3.11 && <0.4,
     QuickCheck >=2.8.1 && <3,
-    --
-    rerebase >=1.1 && <2
+    quickcheck-instances >=0.3.11 && <0.4,
+    random >=1.1 && <2,
+    rerebase >=1.1 && <2,
+    tasty >=1.0.1 && <1.2,
+    tasty-hunit >=0.10 && <0.11,
+    tasty-quickcheck >=0.10 && <0.11
+
+benchmark benchmark
+  type: exitcode-stdio-1.0
+  hs-source-dirs: benchmark
+  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language: Haskell2010
+  ghc-options: -O2 -threaded "-with-rtsopts=-N -A64M"
+  main-is: Main.hs
+  build-depends:
+    criterion >=1.5.1 && <2,
+    potoki-core,
+    rerebase >=1 && <2
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,264 @@
+module Main where
+
+import Prelude hiding (first, second, choose)
+import Control.Arrow
+import Test.QuickCheck.Instances
+import Test.QuickCheck.Monadic as M
+import Test.Tasty
+import Test.Tasty.Runners
+import Test.Tasty.HUnit
+import Test.Tasty.QuickCheck
+import qualified Potoki.Core.IO as C
+import qualified Potoki.Core.Consume as D
+import qualified Potoki.Core.Transform as A
+import qualified Potoki.Core.Produce as E
+import qualified Potoki.Core.Fetch as Fe
+import qualified Data.Attoparsec.ByteString.Char8 as B
+import qualified Data.ByteString as F
+import qualified Data.ByteString.Char8 as I
+import qualified Data.Vector as G
+import qualified System.Random as H
+import qualified Acquire.Acquire as Ac
+import qualified DeferredFolds.Unfoldr as Unfoldr
+import Potoki
+import Transform
+
+main =
+  defaultMain $
+  testGroup "All tests" $
+  [
+    testProperty "unfoldr" $ \ (list :: [Int]) ->
+      list ===
+      unsafePerformIO (C.produceAndConsume (E.unfoldr (Unfoldr.foldable list)) (D.list))
+    ,
+    testGroup "lines extraction props" $ let
+      chunksGen = listOf $ do
+        byteList <- listOf (frequency [(1, pure 10), (20, choose (97, 122))])
+        return (F.pack byteList)
+      in [
+        testProperty "extractLines" $ forAll chunksGen $ \ chunks ->
+          F.split 10 (F.concat chunks) ===
+          unsafePerformIO (C.produceAndConsume (E.transform A.extractLines (E.list chunks)) D.list)
+        ,
+        testProperty "extractLinesWithoutTrail" $ forAll chunksGen $ \ chunks ->
+          I.lines (F.concat chunks) ===
+          unsafePerformIO (C.produceAndConsume (E.transform A.extractLinesWithoutTrail (E.list chunks)) D.list)
+        ,
+        testProperty "extractLinesConcurrently" $ forAll chunksGen $ \ chunks ->
+          F.split 10 (F.concat chunks) ===
+          unsafePerformIO (C.produceAndConsume (E.transform (A.extractLinesConcurrently numCapabilities) (E.list chunks)) D.list)
+      ]
+    ,
+    testCase "extractLinesWithoutTrail" $ do
+      assertEqual "" ["ab", "", "c", "d"] =<<
+        C.produceAndConsume (E.transform A.extractLinesWithoutTrail (E.list ["a", "b\n\nc", "\nd\n"])) D.list
+    ,
+    testCase "extractLines" $ do
+      assertEqual "" ["ab", "", "cd", ""] =<<
+        C.produceAndConsume (E.transform A.extractLines (E.list ["a", "b\n", "\nc", "d\n"])) D.list
+    ,
+    testProperty "list to list" $ \ (list :: [Int]) ->
+    list === unsafePerformIO (C.produceAndConsume (E.list list) D.list)
+    ,
+    testProperty "consecutive consumers" $ \ (list :: [Int], amount) ->
+    list === unsafePerformIO (C.produceAndConsume (E.list list) ((++) <$> D.transform (A.take amount) D.list <*> D.list))
+    ,
+    potoki
+    ,
+    transform
+    ,
+    resourceChecker
+    ,
+    testCase "sync resource checker" $ do
+      resourceVar <- newIORef Initial
+      let produce = syncCheckResource resourceVar
+      C.produceAndConsume produce D.sum
+      fin <- readIORef resourceVar
+      assertEqual "" Released fin
+    ,
+    testCase "async resource checker" $ do
+      resourceVar <- newTVarIO Initial
+      let produce = asyncCheckResource resourceVar
+      potokiThreadId <- forkIO $ do
+          C.produceAndConsume produce D.sum
+          return ()
+      atomically $ do
+        resource <- readTVar resourceVar
+        guard $ resource == Acquired
+      killThread potokiThreadId
+      atomically $ do
+        resource <- readTVar resourceVar
+        guard $ resource == Released
+    ,
+    testProperty "Produce.transform resource checker" $ \ (list :: [Int]) ->
+    let prod = E.list list
+    in monadicIO $ do
+      check <- run $ do
+        resourceVar1 <-  newIORef Initial
+        res <- C.produceAndConsume (E.transform (checkTransform resourceVar1) prod) D.sum
+        readIORef resourceVar1
+      M.assert $ check == Released
+    ,
+    testProperty "Consume.transform resource checker" $ \ (list :: [Int]) ->
+    let prod = E.list list
+    in monadicIO $ do
+      check <- run $ do
+        resourceVar1 <-  newIORef Initial
+        res <- C.produceAndConsume prod (D.transform (checkTransform resourceVar1) D.sum)
+        readIORef resourceVar1
+      M.assert $ check == Released
+    ,
+    testCase "Transform.produce resource checker #1" $ do
+      resourceVar <- newIORef Initial
+      let prod = checkProduce resourceVar (/= Released) 100
+      res1 <- C.produceAndConsume (E.transform (A.produce intToProduce) prod) D.sum
+      res2 <- C.produceAndConsume prod (D.transform (A.produce intToProduce) D.sum)
+      fin <- readIORef resourceVar
+      assertEqual "" Released fin
+      assertEqual "" res1 res2
+    ,
+    testCase "Transform.produce resource checker #2" $ do
+      resourceVar <- newIORef Initial
+      let prod = checkProduce resourceVar (/= Released) 100
+      res1 <- C.produceAndConsume (E.transform (A.take 5 >>> A.produce intToProduce) prod) D.sum
+      res2 <- C.produceAndConsume prod (D.transform (A.take 5 >>> A.produce intToProduce) D.sum)
+      fin <- readIORef resourceVar
+      assertEqual "" Released fin
+      assertEqual "" res1 res2
+  ]
+
+resourceChecker :: TestTree
+resourceChecker =
+  testGroup "produce1 >>= produce2" $
+  [
+    testCase "Check produce binding" $ do
+      resourceVar1 <- newIORef Initial
+      resourceVar2 <- newIORef Initial
+      let prod1 = checkProduce resourceVar1 (const True) 100
+          prod2 = \x -> checkProduce resourceVar2 (/= Released) x
+      res <- C.produceAndConsume (prod1 >>= prod2) D.sum
+      fin <- readIORef resourceVar1
+      assertEqual "" Released fin
+    ,
+    testProperty "Bind for produce" $ \ (list :: [Int]) ->
+    let check = list >>= (enumFromTo 0)
+        prod1 = E.list list
+        prod2 = \x -> E.list $ enumFromTo 0 x
+    in monadicIO $ do
+      res <- run $ C.produceAndConsume (prod1 >>= prod2) D.list
+      M.assert (check == res)
+    ,
+    testProperty "liftIO for Produce. Consume0" $ \ (_ :: Int) ->
+    monadicIO $ do
+      check <- run $ do
+        checkVar <- newIORef False
+        let prod = liftIO $ writeIORef checkVar True
+        C.produceAndConsume prod someThing
+        readIORef checkVar
+      M.assert $ check == False
+    ,
+    testProperty "liftIO for Produce. ConsumeN" $ \ (n :: Int) ->
+    monadicIO $ do
+      let prod = liftIO (return n)
+      len <- run (C.produceAndConsume prod D.count)
+      M.assert (len == 1)
+    ,
+    testCase "Check mergeOrdering" $ do
+      resourceVar1 <- newIORef Initial
+      resourceVar2 <- newIORef Initial
+      let prod1 = checkProduce resourceVar1 (/= Released) 100
+          prod2 = checkProduce resourceVar2 (/= Released) 50
+      res <- C.produceAndConsume (E.mergeOrdering (\a b -> a <= b) prod1 prod2) D.list  
+      fin1 <- readIORef resourceVar1
+      fin2 <- readIORef resourceVar2
+      assertEqual "" Released fin1
+      assertEqual "" Released fin2
+    ]
+
+intToProduce :: Int -> E.Produce Int
+intToProduce a = E.Produce . Ac.Acquire $ do
+  stVar <- newIORef 0
+  return $ flip (,) (return ()) $ Fe.Fetch $ do
+      n <- readIORef stVar
+      if n >= a + 1 then (return Nothing)
+      else do
+        writeIORef stVar $! n + 1
+        return (Just n)
+
+someThing :: D.Consume input Int
+someThing = D.Consume $ \ (Fe.Fetch _) -> return 0
+
+single :: Foldable f => f a -> Maybe a
+single = join . (foldr' f Nothing)
+  where
+    f x Nothing = Just $ Just x
+    f _ _       = Just Nothing
+
+data Resource
+  = Initial
+  | Acquired
+  | Released
+  | AcquiredImproperly
+  | ReleasedImproperly
+  deriving (Show, Eq)
+
+checkTransform :: IORef Resource -> A.Transform Int Int
+checkTransform resourceVar = A.Transform $ \ fetchIO -> Ac.Acquire $ do
+  writeIORef resourceVar Acquired
+  return $ (,) (plusFetch fetchIO) $ do
+      res <- readIORef resourceVar
+      case res of
+        Acquired -> writeIORef resourceVar Released
+        _ -> writeIORef resourceVar ReleasedImproperly
+
+plusFetch :: Fe.Fetch Int -> Fe.Fetch Int
+plusFetch (Fe.Fetch fetchIO) = Fe.Fetch $ do
+  fetch <- fetchIO
+  return $ fmap succ fetch
+
+checkProduce :: IORef Resource -> (Resource -> Bool) -> Int -> E.Produce Int
+checkProduce resourceVar f k = E.Produce . Ac.Acquire $ do
+  res <- readIORef resourceVar
+  if f res && res /= Initial
+    then do
+      return $ (,) (Fe.Fetch $ return Nothing) $ writeIORef resourceVar AcquiredImproperly
+    else do
+      writeIORef resourceVar Acquired
+      stVar <- newIORef 0
+      let fetch = do
+            n <- readIORef stVar
+            if n >= k then return (Nothing)
+            else do
+              writeIORef stVar $! n + 1
+              return (Just n)
+      return $ (,) (Fe.Fetch fetch) $ do
+          res <- readIORef resourceVar
+          case res of
+            Acquired -> writeIORef resourceVar Released
+            _ -> writeIORef resourceVar ReleasedImproperly
+
+asyncCheckResource :: TVar Resource -> E.Produce Int
+asyncCheckResource resourceVar = E.Produce . Ac.Acquire $ do
+  atomically $ writeTVar resourceVar Acquired
+  stVar <- newIORef 0
+  let fetch = do
+        n <- readIORef stVar
+        writeIORef stVar $! n + 1
+        return (Just n)
+  return (Fe.Fetch fetch, atomically $ writeTVar resourceVar Released)
+
+syncCheckResource :: IORef Resource -> E.Produce Int
+syncCheckResource resourceVar = E.Produce . Ac.Acquire $ do
+  writeIORef resourceVar Acquired
+  stVar <- newIORef 0
+  let fetch = do
+        n <- readIORef stVar
+        if n >= 1000 then return (Nothing)
+        else do
+          writeIORef stVar $! n + 1
+          return (Just n)
+  return $ (,) (Fe.Fetch fetch) $ do
+      res <- readIORef resourceVar
+      case res of
+        Acquired -> writeIORef resourceVar Released
+        _ -> writeIORef resourceVar ReleasedImproperly
diff --git a/test/Potoki.hs b/test/Potoki.hs
new file mode 100644
--- /dev/null
+++ b/test/Potoki.hs
@@ -0,0 +1,309 @@
+module Potoki where
+
+import Prelude hiding (first, second)
+import Control.Arrow
+import Test.QuickCheck.Instances
+import Test.QuickCheck.Monadic as M
+import Test.Tasty
+import Test.Tasty.Runners
+import Test.Tasty.HUnit
+import Test.Tasty.QuickCheck
+import qualified Control.Foldl as Fl
+import qualified Potoki.Core.IO as C
+import qualified Potoki.Core.Consume as D
+import qualified Potoki.Core.Transform as A
+import qualified Potoki.Core.Produce as E
+import qualified Data.Attoparsec.ByteString.Char8 as B
+import qualified Data.ByteString as F
+import qualified Data.Vector as G
+import qualified System.Random as H
+import Data.List.Index (indexed)
+
+potoki :: TestTree
+potoki =
+  testGroup "All tests for potoki's end-users functions" $
+  [
+    testCase "vector to list" $ do
+      result <- C.produceAndConsume (E.vector (G.fromList [1,2,3])) (D.list)
+      assertEqual "" [1,2,3] result
+    ,
+    testCase "just" $ do
+      result <- C.produceAndConsume (E.list [Just 1, Nothing, Just 2]) (D.transform A.just D.list)
+      assertEqual "" [1,2] result
+    ,
+    testCase "transform,consume,take" $ do
+      let
+        transform = A.consume (D.transform (A.take 3) D.list)
+        consume = D.transform transform D.list
+        produceAndConsume list = C.produceAndConsume (E.list list) (consume)
+      assertEqual "" [[1,2,3], [4,5,6], [7,8]] =<< produceAndConsume [1,2,3,4,5,6,7,8]
+      assertEqual "" [[1,2,3], [4,5,6], [7,8,9]] =<< produceAndConsume [1,2,3,4,5,6,7,8,9]
+      assertEqual "" [] =<< produceAndConsume ([] :: [Int])
+    ,
+    testCase "File reading" $ do
+      let produce =
+            E.transform (arr (either (const Nothing) Just) >>> A.just) $
+            E.fileBytes "samples/1"
+      result <- C.produceAndConsume produce (fmap F.length D.concat)
+      assertEqual "" 17400 result
+    ,
+    testCase "mergeOrdering produce testCase" $ do
+      let
+        consume = D.list
+        produceAndConsume list1 list2 = C.produceAndConsume (E.mergeOrdering (\a b -> a <= b) (E.list list1) (E.list list2)) (consume)
+      assertEqual "" [1,2,3,4,5,6,7,8,9,10] =<< produceAndConsume [1,3,5,7,9] [2,4,6,8,10]
+      assertEqual "" [1,2,3,4,5,6,7,8,9,10] =<< produceAndConsume [2,4,6,8,10] [1,3,5,7,9]
+      assertEqual "" [1,2,2,3,3,4,5,6,7,8,8,9,9,10] =<< produceAndConsume [1,2,3,8,9,10] [2,3,4,5,6,7,8,9]
+      assertEqual "" [1,2,3,4,5,6,7,8,9,10] =<< produceAndConsume [1,2,3] [4,5,6,7,8,9,10]
+      assertEqual "" [1,2,3,4,5,6,7,8,9,10] =<< produceAndConsume [4,5,6] [1,2,3,7,8,9,10]
+      assertEqual "" [1,2,3,4,5,6,7,8,9,10] =<< produceAndConsume [1,2,3,7,8,9,10] [4,5,6]
+      assertEqual "" [1,2,3,4,5,6,7,8,9,10] =<< produceAndConsume [1,2,3,4,5,6,7,8,9,10] []
+      assertEqual "" [] =<< produceAndConsume ([] :: [Int]) ([] :: [Int]) 
+    ,
+    testProperty "mergeOrdering produce" $ \ (l1 :: [Int], l2 :: [Int]) -> 
+    let 
+      list1 = sort l1
+      list2 = sort l2
+      list = sort $ l1 ++ l2
+    in list === unsafePerformIO (C.produceAndConsume (E.mergeOrdering (\a b -> a <= b) (E.list list1) (E.list list2)) D.list)
+    ,
+    transformPotoki
+    ,
+    parsingPotoki
+    ,
+    consumePotoki
+  ]
+
+
+transformPotoki :: TestTree
+transformPotoki =
+  testGroup "Transform" $
+  [
+    testCase "Order" $ do
+      let
+        list = [Left 1, Left 2, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 4, Left 3]
+        transform = left (A.consume (D.transform (A.take 2) D.sum))
+      result <- C.produceAndConsume (E.list list) (D.transform transform D.list)
+      assertEqual "" [Left 3, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 7] result
+    ,
+    testCase "Interrupted order" $ do
+      let
+        list = [Left 1, Left 2, Right 'a']
+        transform = left (A.consume (D.transform (A.take 3) D.sum))
+      result <- C.produceAndConsume (E.list list) (D.transform transform D.list)
+      assertEqual "" [Left 3, Right 'a'] result
+    ,
+    testCase "Distinct" $ do
+      let
+        list = [1,2,3,2,3,2,1,4,1] :: [Int]
+      result <- C.produceAndConsume (E.list list) (D.transform A.distinct D.list)
+      assertEqual "" [1,2,3,4] result
+    ,
+    testCase "Distinct By" $ do
+      let
+        list = [(1, ""),(2, ""),(3, ""),(2, ""),(3, ""),(2, ""),(1, ""),(4, ""),(1, "")] :: [(Int, String)]
+      result <- C.produceAndConsume (E.list list) (D.transform (A.distinctBy fst) D.list)
+      assertEqual "" [(1, ""),(2, ""),(3, ""),(4, "")] result
+    ,
+    testCase "Concurrently" $ do
+      let
+        list = [1..20000] :: [Int]
+        produce = E.list list
+        transform =
+          A.concurrently 12 $
+          arr (\ x -> H.randomRIO (0, 100) >>= threadDelay >> return x) >>>
+          A.executeIO
+        consume = D.transform transform D.list
+      result <- C.produceAndConsume produce consume
+      assertBool "Is dispersed" (list /= result)
+      assertEqual "Contains no duplicates" 0 (length result - length (nub result))
+      assertEqual "Equals the original once sorted" list (sort result)
+    ,
+    testProperty "Line" $ \ chunks ->
+    let
+      expected =
+        mconcat chunks
+      actual =
+        unsafePerformIO (C.produceAndConsume produce consume)
+        where
+          produce =
+            E.list chunks
+          consume =
+            rmap (mconcat . intersperse "\n") $
+            D.transform A.extractLines D.list
+      in expected === actual
+    ,
+    testProperty "takeWhile" $ \ (list :: [Int]) ->
+    let listPart = takeWhile odd list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndTransformAndConsume prod (A.takeWhile odd) D.list)
+      M.assert (res == listPart)
+    ,
+    testProperty "mapFilter" $ \ (list :: [Int]) ->
+    let in2MaybeOut input =
+          if input `mod` 4 == 0
+            then Just $ input `mod` 4
+            else Nothing
+        filteredList = map fromJust . filter (/= Nothing) . map in2MaybeOut $ list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndTransformAndConsume prod (A.mapFilter in2MaybeOut) D.list)
+      M.assert (res == filteredList)
+    ,
+    testProperty "filter" $ \ (list :: [Int]) ->
+    let filteredList = filter even list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndTransformAndConsume prod (A.filter even) D.list)
+      M.assert (res == filteredList)
+    ,
+    testCase "uniquify" $ do
+      let list = [1,1,2,3,4,5,5,5,6,7,8,9,10]
+      res <- C.produceAndTransformAndConsume (E.list list) (A.uniquify) D.list
+      assertEqual "" [1,2,3,4,5,6,7,8,9,10] res
+    ,
+    testProperty "uniquify unit test" $ \ (list :: [Int]) ->
+    let sortList = sort list
+        uniquifyList = nub sortList
+    in uniquifyList === unsafePerformIO (C.produceAndTransformAndConsume (E.list sortList) (A.uniquify) D.list)
+  ]
+
+parsingPotoki :: TestTree
+parsingPotoki =
+  testGroup "Parsing" $
+  [
+    testCase "Sample 1" $ do
+      let parser = B.double <* B.char ','
+          transform = arr (either (const Nothing) Just) >>> A.just >>> A.parseBytes parser
+          produce = E.transform transform (E.fileBytes "samples/1")
+      result <- C.produceAndConsume produce D.count
+      assertEqual "" 4350 result
+    ,
+    testCase "Sample 1 greedy" $ do
+      let parser = B.sepBy B.double (B.char ',')
+          transform = arr (either (const Nothing) Just) >>> A.just >>> A.parseBytes parser
+          produce = E.transform transform (E.fileBytes "samples/1")
+      result <- C.produceAndConsume produce D.list
+      assertEqual "" [Right 4350] (fmap (fmap length) result)
+    ,
+    testCase "Split chunk" $
+    let
+      produce = E.list ["1", "2", "3"]
+      parser = B.anyChar
+      transform = A.parseBytes parser >>> arr (either (const Nothing) Just) >>> A.just
+      consume = D.transform transform D.count
+      in do
+        assertEqual "" 3 =<< C.produceAndConsume produce consume
+  ]
+
+consumePotoki =
+  testGroup "Consume" $
+  [
+    testProperty "count" $ \ (list :: [Int]) ->
+    let n = length list
+    in monadicIO $ do
+      let prod = E.list list
+      len <- run (C.produceAndConsume prod D.count)
+      M.assert (len == n)
+    ,
+    testProperty "sum" $ \ (list :: [Int]) ->
+    let n = sum list
+    in monadicIO $ do
+      let prod = E.list list
+      len <- run (C.produceAndConsume prod D.sum)
+      M.assert (len == n)
+    ,
+    testProperty "head" $ \ (list :: [Int]) ->
+    let el = if null list then Nothing else (Just (head list))
+    in monadicIO $ do
+      let prod = E.list list
+      he <- run (C.produceAndConsume prod D.head)
+      M.assert (he == el)
+    ,
+    testProperty "last" $ \ (list :: [Int]) ->
+    let el = if null list then Nothing else (Just (last list))
+    in monadicIO $ do
+      let prod = E.list list
+      he <- run (C.produceAndConsume prod D.last)
+      M.assert (he == el)
+    ,
+    testProperty "list" $ \ (list :: [Int]) ->
+    monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndConsume prod D.list)
+      M.assert (res == list)
+    ,
+    testProperty "reverseList" $ \ (list :: [Int]) ->
+    let revList = reverse list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndConsume prod D.reverseList)
+      M.assert (res == revList)
+    ,
+    testProperty "vector" $ \ (list :: [Int]) ->
+    let vec = G.fromList list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndConsume prod D.vector)
+      M.assert (res == vec)
+    ,
+    testProperty "concat" $ \ (list :: [[Int]]) ->
+    let con = concat list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndConsume prod D.concat)
+      M.assert (res == con)
+    ,
+    testProperty "fold" $ \ (list :: [Int], fun :: (Fun (Int, Int) Int), first :: Int) ->
+    let f = applyFun2 fun
+        fol = foldl' f first list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndConsume prod (D.fold (Fl.Fold f first id)))
+      M.assert (res == fol)
+    ,
+    testProperty "foldInIO" $ \ (list :: [Int]) ->
+    let fin = sum list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run $ do
+        sumVar <- newIORef 0
+        (C.produceAndConsume prod (D.foldInIO (Fl.FoldM (\_ a -> modifyIORef' sumVar (a+)) (pure ()) (\_ -> readIORef sumVar)) ) )
+      M.assert (res == fin)
+    ,
+    testProperty "folding" $ \ (list :: [Int], fun :: (Fun (Int, Int) Int), first :: Int) ->
+    let f = applyFun2 fun
+        fol = foldl' f first list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndConsume prod (D.folding (Fl.Fold f first id) (D.sum) ))
+      a <- run (C.produceAndConsume prod D.sum)
+      M.assert (res == (fol, a))
+    ,
+    testProperty "foldingInIO" $ \ (list :: [Int]) ->
+    let fol = sum list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run $ do
+        sumVar <- newIORef 0
+        (C.produceAndConsume prod (D.foldingInIO (Fl.FoldM (\_ a -> modifyIORef' sumVar (a+)) (pure ()) (\_ -> readIORef sumVar)) (D.sum) ))
+      a <- run (C.produceAndConsume prod D.sum)
+      M.assert (res == (fol, a))
+    ,
+    testProperty "execState" $ \ (list :: [Int]) ->
+    let f = modify' . (+)
+        resL = sum list
+    in monadicIO $ do
+      let prod = E.list list
+      res <- run (C.produceAndConsume prod (D.execState f 0))
+      M.assert (res == resL)
+    ,
+    testProperty "Choice consume right'" $ \ (list :: [Either Bool Int]) ->
+    let n = sum <$> sequence list
+    in monadicIO $ do
+      let prod = E.list list
+      len <- run (C.produceAndConsume prod $ right' D.sum)
+      M.assert (len == n)
+  ]
+
diff --git a/test/Transform.hs b/test/Transform.hs
new file mode 100644
--- /dev/null
+++ b/test/Transform.hs
@@ -0,0 +1,215 @@
+module Transform where
+
+import Prelude hiding (first, second, choose)
+import Control.Arrow
+import Test.QuickCheck.Instances
+import Test.Tasty
+import Test.Tasty.Runners
+import Test.Tasty.HUnit
+import Test.Tasty.QuickCheck
+import qualified Potoki.Core.IO as C
+import qualified Potoki.Core.Consume as D
+import qualified Potoki.Core.Transform as A
+import qualified Potoki.Core.Produce as E
+import qualified Data.Attoparsec.ByteString.Char8 as B
+import qualified Data.ByteString as F
+import qualified Data.Vector as G
+import qualified Data.List.Split as SplitList
+import qualified System.Random as H
+
+transform :: TestTree
+transform =
+  testGroup "Transform" $
+  [
+    testGroup "concurrentlyInOrder unit tests" $ let
+      testList (list :: [[Int]]) =
+        testCase (show list) $ do
+          actual <- C.produceAndTransformAndConsume (E.list list) (A.concurrentlyInOrder 4 A.list) D.list
+          assertEqual "" (concat list) actual
+      in [
+          testList [[]] ,
+          testList [[],[]],
+          testList [[0, 1, 2], []],
+          testList [[0, 1, 2], [3]],
+          testList [[0,3,7,-2],[],[],[-8,7,-8,7,3,-4,1,4],[-4,1],[5,-2,3,-2,-5,6,-7],[-8,6,7,1,8,-8,5]]
+        ]
+    ,
+    testProperty "concurrentlyInOrder" $ \ (list :: [[Int]]) ->
+      concat list ===
+      unsafePerformIO (C.produceAndTransformAndConsume (E.list list) (A.concurrentlyInOrder 4 A.list) D.list)
+    ,
+    testProperty "list" $ \ (list :: [[Int]]) -> 
+      concat list ===
+      unsafePerformIO (C.produceAndTransformAndConsume (E.list list) A.list D.list)
+    ,
+    testProperty "drop" $ \ (list :: [Int], n :: Int) -> 
+      (drop n list) ===
+      unsafePerformIO (C.produceAndTransformAndConsume (E.list list) (A.drop n) D.list)
+    ,
+    testProperty "Applying chunksOf to list has the same effect as the \"batch\" transform" $ let
+      gen = do
+        list <- listOf (choose (0, 1000 :: Int))
+        batchSize <- frequency [(1000, choose (1, 3)), (100, choose (4, 100)), (1, pure 0)]
+        traceShowM (list, batchSize)
+        return (list, batchSize)
+      in forAll gen $ \ (list, batchSize) -> let
+        listChunks = if batchSize < 1 then [] else SplitList.chunksOf batchSize list
+        potokiChunks = unsafePerformIO $ C.produceAndTransformAndConsume (E.list list) (rmap toList (A.batch @Vector batchSize)) D.list
+        in listChunks === potokiChunks
+    ,
+    transformProduce
+    ,
+    transformChoice
+    ,
+    transformArrowLaws
+  ]
+
+transformProduce =
+  testGroup "Produce" $
+  [
+    testCase "1" $ do
+      let
+        list = [1, 2, 3] :: [Int]
+      result <- C.produceAndTransformAndConsume
+        (E.list list)
+        (A.produce (E.list . \ n -> flip replicate n n))
+        (D.list)
+      assertEqual "" [1, 2, 2, 3, 3, 3] result
+  ,
+    testCase "2" $ do
+      let
+        list = [1, 2, 3] :: [Int]
+      result <- C.produceAndTransformAndConsume
+        (E.list list)
+        (A.produce (E.list . \ n -> [(n, n)]))
+        (D.list)
+      assertEqual "" [(1, 1), (2, 2), (3, 3)] result
+  ,
+    testCase "3" $ do
+      let
+        list = [1, 2, 3] :: [Int]
+      result <- C.produceAndTransformAndConsume
+        (E.list list)
+        (A.produce (E.list . \ n -> [n, n]))
+        (D.list)
+      assertEqual "" [1, 1, 2, 2, 3, 3] result
+  ]
+
+transformChoice =
+  testGroup "Choice" $
+  [
+    testCase "1" $ do
+      let
+        list = [Left 1, Left 2, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 4, Left 3]
+        transform = left' id
+      result <- C.produceAndTransformAndConsume (E.list list) transform D.list
+      assertEqual "" [Left 1, Left 2, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 4, Left 3] result
+    ,
+    testCase "2" $ do
+      let
+        list = [Left 1, Left 2, Right 'z', Right 'a', Right 'b', Left 0, Right 'x', Left 4, Left 3]
+        transform = right' (A.consume D.list)
+      result <- C.produceAndTransformAndConsume (E.list list) transform D.list
+      assertEqual "" [Left 1, Left 2, Right "zab", Left 0, Right "x", Left 4, Left 3] result
+    ,
+    testCase "3" $ do
+      let
+        list = [Left 4, Right 'z', Right 'a', Left 3, Right 'b', Left 0, Left 1, Right 'x', Left 4, Left 3]
+        transform = left' (A.consume D.list)
+      result <- C.produceAndTransformAndConsume (E.list list) transform D.list
+      assertEqual "" [Left [4], Right 'z', Right 'a', Left [3], Right 'b', Left [0, 1], Right 'x', Left [4, 3]] result
+  ]
+
+transformArrowLaws =
+  testGroup "Arrow laws"
+  [
+    testGroup "Strong"
+    [
+      testCase "1" $ do
+        let
+          input = [(1,'a'),(2,'b'),(3,'c'),(4,'d')]
+          transform = first transform1
+        result <- C.produceAndTransformAndConsume (E.list input) transform D.list
+        assertEqual "" [(6,'c'),(4,'d')] result
+      ,
+      testCase "Lack of elements" $ do
+        let
+          input = [(1,'a'),(2,'b')]
+          transform = first transform1
+        result <- C.produceAndTransformAndConsume (E.list input) transform D.list
+        assertEqual "" [(3,'b')] result
+    ]
+    ,
+    transformProperty "arr id = id"
+      (arr id :: A.Transform Int Int)
+      id
+    ,
+    transformProperty "arr (f >>> g) = arr f >>> arr g"
+      (arr (f >>> g))
+      (arr f >>> arr g)
+    ,
+    transformProperty "first (arr f) = arr (first f)"
+      (first (arr f) :: A.Transform (Int, Char) (Int, Char))
+      (arr (first f))
+    ,
+    transformProperty "first (f >>> g) = first f >>> first g"
+      (first (transform1 >>> transform2) :: A.Transform (Int, Char) (Int, Char))
+      (first (transform1) >>> first (transform2))
+    ,
+    transformProperty "first f >>> arr fst = arr fst >>> f"
+      (first transform1 >>> arr fst :: A.Transform (Int, Char) Int)
+      (arr fst >>> transform1)
+    ,
+    transformProperty "first f >>> arr (id *** g) = arr (id *** g) >>> first f"
+      (first transform1 >>> arr (id *** g))
+      (arr (id *** g) >>> first transform1)
+    ,
+    transformProperty "first (first f) >>> arr assoc = arr assoc >>> first f"
+      (first (first transform1) >>> arr assoc :: A.Transform ((Int, Char), Double) (Int, (Char, Double)))
+      (arr assoc >>> first transform1)
+    ,
+    transformProperty "left (arr f) = arr (left f)"
+      (left (arr f) :: A.Transform (Either Int Char) (Either Int Char))
+      (arr (left f))
+    ,
+    transformProperty "left (f >>> g) = left f >>> left g"
+      (left (transform1 >>> transform2) :: A.Transform (Either Int Char) (Either Int Char))
+      (left (transform1) >>> left (transform2))
+    ,
+    transformProperty "f >>> arr Left = arr Left >>> left f"
+      (transform1 >>> arr Left :: A.Transform Int (Either Int Char))
+      (arr Left >>> left transform1)
+    ,
+    transformProperty "left f >>> arr (id +++ g) = arr (id +++ g) >>> left f"
+      (left transform1 >>> arr (id +++ g))
+      (arr (id +++ g) >>> left transform1)
+    ,
+    transformProperty "left (left f) >>> arr assocsum = arr assocsum >>> left f"
+      (left (left transform1) >>> arr assocsum :: A.Transform (Either (Either Int Char) Double) (Either Int (Either Char Double)))
+      (arr assocsum >>> left transform1)
+    ,
+    transformProperty "left (left (arr f)) >>> arr assocsum = arr assocsum >>> left (arr f)"
+      (left (left (arr f)) >>> arr assocsum :: A.Transform (Either (Either Int Char) Double) (Either Int (Either Char Double)))
+      (arr assocsum >>> left (arr f))
+  ]
+  where
+    f = (+24) :: Int -> Int
+    g = (*3) :: Int -> Int
+    transform1 = A.consume (D.transform (A.take 3) D.sum) :: A.Transform Int Int
+    transform2 = A.consume (D.transform (A.take 4) D.sum) :: A.Transform Int Int
+    assoc ((a,b),c) = (a,(b,c))
+    assocsum (Left (Left x)) = Left x
+    assocsum (Left (Right y)) = Right (Left y)
+    assocsum (Right z) = Right (Right z)
+
+transformProperty ::
+  (Arbitrary input, Show input, Eq output, Show output) =>
+  String -> A.Transform input output -> A.Transform input output -> TestTree
+transformProperty name leftTransform rightTransform =
+  testProperty name property
+  where
+    property list =
+      transform leftTransform === transform rightTransform
+      where
+        transform transform =
+          unsafePerformIO (C.produceAndTransformAndConsume (E.list list) transform D.list)
diff --git a/tests/Main.hs b/tests/Main.hs
deleted file mode 100644
--- a/tests/Main.hs
+++ /dev/null
@@ -1,149 +0,0 @@
-module Main where
-
-import Prelude hiding (first, second)
-import Control.Arrow
-import Test.QuickCheck.Instances
-import Test.Tasty
-import Test.Tasty.Runners
-import Test.Tasty.HUnit
-import Test.Tasty.QuickCheck
-import qualified Potoki.Core.IO as C
-import qualified Potoki.Core.Consume as D
-import qualified Potoki.Core.Transform as A
-import qualified Potoki.Core.Produce as E
-import qualified Data.Vector as G
-
-
-main =
-  defaultMain $
-  testGroup "All tests" $
-  [
-    testProperty "list to list" $ \ (list :: [Int]) ->
-    list === unsafePerformIO (C.produceAndConsume (E.list list) D.list)
-    ,
-    testProperty "Dual consumption" $ \ (list :: [Int]) ->
-    (list, list) === unsafePerformIO (C.produceAndConsume (E.list list) ((,) <$> D.list <*> D.list))
-    ,
-    transform
-  ]
-
-transform =
-  testGroup "Transform" $
-  [
-    transformProduce
-    ,
-    transformChoice
-    ,
-    transformArrowLaws
-  ]
-
-transformProduce =
-  testCase "Produce" $ do
-    let list = [1, 2, 3] :: [Int]
-    result <- C.produceAndTransformAndConsume
-      (E.list list)
-      (A.produce (E.list . \ n -> flip replicate n n))
-      (D.list)
-    assertEqual "" [1, 2, 2, 3, 3, 3] result
-
-transformChoice =
-  testGroup "Choice" $
-  [
-    testCase "1" $ do
-      let
-        list = [Left 1, Left 2, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 4, Left 3]
-        transform = left' id
-      result <- C.produceAndTransformAndConsume (E.list list) transform D.list
-      assertEqual "" [Left 1, Left 2, Right 'z', Left 2, Right 'a', Left 1, Right 'b', Left 0, Right 'x', Left 4, Left 3] result
-    ,
-    testCase "2" $ do
-      let
-        list = [Left 1, Left 2, Right 'z', Right 'a', Right 'b', Left 0, Right 'x', Left 4, Left 3]
-        transform = right (A.consume D.list)
-      result <- C.produceAndTransformAndConsume (E.list list) transform D.list
-      assertEqual "" [Left 1, Left 2, Right "zab", Left 0, Right "x", Left 4, Left 3] result
-    ,
-    testCase "3" $ do
-      let
-        list = [Right 'z', Right 'a', Left 3, Right 'b', Left 0, Left 1, Right 'x', Left 4, Left 3]
-        transform = left (A.consume D.list)
-      result <- C.produceAndTransformAndConsume (E.list list) transform D.list
-      assertEqual "" [Right 'z', Right 'a', Left [3], Right 'b', Left [0, 1], Right 'x', Left [4, 3]] result
-  ]
-
-transformArrowLaws =
-  testGroup "Arrow laws"
-  [
-    transformProperty "arr id = id"
-      (arr id :: A.Transform Int Int)
-      id
-    ,
-    transformProperty "arr (f >>> g) = arr f >>> arr g"
-      (arr (f >>> g))
-      (arr f >>> arr g)
-    ,
-    transformProperty "first (arr f) = arr (first f)"
-      (first (arr f) :: A.Transform (Int, Char) (Int, Char))
-      (arr (first f))
-    ,
-    transformProperty "first (f >>> g) = first f >>> first g"
-      (first (transform1 >>> transform2) :: A.Transform (Int, Char) (Int, Char))
-      (first (transform1) >>> first (transform2))
-    ,
-    transformProperty "first f >>> arr fst = arr fst >>> f"
-      (first transform1 >>> arr fst :: A.Transform (Int, Char) Int)
-      (arr fst >>> transform1)
-    ,
-    transformProperty "first f >>> arr (id *** g) = arr (id *** g) >>> first f"
-      (first transform1 >>> arr (id *** g))
-      (arr (id *** g) >>> first transform1)
-    ,
-    transformProperty "first (first f) >>> arr assoc = arr assoc >>> first f"
-      (first (first transform1) >>> arr assoc :: A.Transform ((Int, Char), Double) (Int, (Char, Double)))
-      (arr assoc >>> first transform1)
-    ,
-    transformProperty "left (arr f) = arr (left f)"
-      (left (arr f) :: A.Transform (Either Int Char) (Either Int Char))
-      (arr (left f))
-    ,
-    transformProperty "left (f >>> g) = left f >>> left g"
-      (left (transform1 >>> transform2) :: A.Transform (Either Int Char) (Either Int Char))
-      (left (transform1) >>> left (transform2))
-    ,
-    transformProperty "f >>> arr Left = arr Left >>> left f"
-      (transform1 >>> arr Left :: A.Transform Int (Either Int Char))
-      (arr Left >>> left transform1)
-    ,
-    transformProperty "left f >>> arr (id +++ g) = arr (id +++ g) >>> left f"
-      (left transform1 >>> arr (id +++ g))
-      (arr (id +++ g) >>> left transform1)
-    ,
-    transformProperty "left (left f) >>> arr assocsum = arr assocsum >>> left f"
-      (left (left transform1) >>> arr assocsum :: A.Transform (Either (Either Int Char) Double) (Either Int (Either Char Double)))
-      (arr assocsum >>> left transform1)
-    ,
-    transformProperty "left (left (arr f)) >>> arr assocsum = arr assocsum >>> left (arr f)"
-      (left (left (arr f)) >>> arr assocsum :: A.Transform (Either (Either Int Char) Double) (Either Int (Either Char Double)))
-      (arr assocsum >>> left (arr f))
-  ]
-  where
-    f = (+24) :: Int -> Int
-    g = (*3) :: Int -> Int
-    transform1 = A.take 3 >>> A.consume D.sum :: A.Transform Int Int
-    transform2 = A.take 4 >>> A.consume D.sum :: A.Transform Int Int
-    assoc ((a,b),c) = (a,(b,c))
-    assocsum (Left (Left x)) = Left x
-    assocsum (Left (Right y)) = Right (Left y)
-    assocsum (Right z) = Right (Right z)
-
-transformProperty :: 
-  (Arbitrary input, Show input, Eq output, Show output) => 
-  String -> A.Transform input output -> A.Transform input output -> TestTree
-transformProperty name leftTransform rightTransform =
-  testProperty name property
-  where
-    property list =
-      transform leftTransform === transform rightTransform
-      where
-        transform transform =
-          unsafePerformIO (C.produceAndTransformAndConsume (E.list list) transform D.list)
