You are trying to rethink pipes . Your source and yield are await and yield pipes. The other two problems you are trying to handle are ReaderT and WriterT respectively. If you put the entire list of inputs into the ReaderT environment, you can run the local subcategories that start at the beginning of the list. You can collect all the results from the count by adding a WriterT layer to collect the result.
For good syntax, with gather you are trying to recreate ListT .
Pipes, Readers, and Writers
We will use all of the following in a very short order.
import Data.Functor.Identity import Data.Foldable import Control.Monad import Control.Monad.Morph import Control.Monad.IO.Class import Control.Monad.Trans.Class import Control.Monad.Trans.Reader hiding (local) import Control.Monad.Trans.Writer.Strict import Pipes.Core import Pipes ((>->)) import qualified Pipes as P import qualified Pipes.Prelude as P import Pipes.Lift (runWriterP, runReaderP)
Your builder is Pipe io on top of Reader [i] , which allows you to reset at the beginning of input. We will define two versions of it: BuildT , which is a monad transformer, and BuildM , which is a monad. BuildM is a transformer applied to Identity .
type BuildT eiomr = Pipe io (ReaderT em) r type BuildM eior = BuildT eio Identity r
local starts the builder, which gives it all the input read from the environment. We might want to give it a different name to avoid a conflict with local defined for ReaderT
local :: (Monad m, Foldable f) => BuildT (fi) iom () -> Proxy a' a () o (ReaderT (fi) m) () local subDef = do e <- lift ask hoist lift $ runReaderP e $ P.each e >-> subDef
To collect the results of auxiliary calculations, we will take advantage of the fact that the pipes are so clean that you can change the original monad if you have a natural forall x. mx -> nx transformation forall x. mx -> nx forall x. mx -> nx . Proxies from pipes have an MFunctor instance that provides the function hoist :: (forall x. mx -> nx) -> Proxy a' ab' bmr -> Proxy a' ab' bnr ; it allows us to lift all the basic monad operations under the pipe to use the pipe over another transformer, in this case WriterT .
collect :: (Monad m) => Proxy a' a () bmr -> Proxy a' ac' cm ([b], r) collect subDef = do (r, w) <- runWriterP $ hoist lift subDef //> \x -> lift $ tell (++[x]) return (w [], r)
To start the builder, we download all the input from the environment, provide the initial environment, collect the results and run the entire channel.
runBuildT :: (Monad m) => [i] -> BuildT [i] iom () -> m [o] runBuildT e = runEffect . fmap fst . collect . runReaderP e . local
Running a monad instead of a transformer is easy
runBuildM :: [i] -> BuildM [i] io () -> [o] runBuildM e = runIdentity . runBuildT e
Listt
In this section, we can use do -notation to create all combinations of things. This is equivalent to using for pipes instead of every >>= and yield instead of every return .
The syntax is that gather all the sub- ListT results reinvent the ListT . a ListT ma contains a Producer am () , which returns only data downstream. Pipes that receive data from upstream and downstream data do not fit into Producer bm () . It takes a little conversion.
We can convert a Proxy that has both an upstream and downstream interface into one, only with an upstream interface wrapped around another proxy with an upstream interface. To do this, we will raise the main monad to our new internal proxy server, and then replace all request in the external proxy server below the request removed from the internal proxy server.
floatRespond :: (Monad m) => Proxy a' ab' bmr -> Proxy c' cb' b (Proxy a' ad' dm) r floatRespond = (lift . request >\\) . hoist lift
They can be converted to ListT . We will discard any returned data to get a more polymorphic type.
gather :: (Monad m) => Proxy a' a () bmr -> P.ListT (Proxy a' ac' cm) b gather = P.Select . floatRespond . (>>= return . const ())
Using ListT little cumbersome to use; you need mplus between return to get both outputs. It is often convenient to translate the proxy server into ListT so you can lift . yield lift . yield instead of return ing. We are going to drop all our ListT results, relying on the result coming from lift . yield. lift . yield. enumerate just runs a ListT` wrapped around something, discarding all results
enumerate = P.runListT
Example
Now we are ready to write and run your example. I want to say that for source you need to get one value from the source, and for yield - one value. If you do not need to get values one at a time, your question is too high, and this answer is redundant.
source = P.await yield = P.yield
In the example where we use gather to create lists, we run this piece of code with enumerate and get the results with lift . yield lift . yield .
import Data.Char build_tests :: Monad m => Int -> BuildT [String] String String m () build_tests depth = do local $ do v <- source yield $ v yield $ (map toLower v) yield "[]" yield "()" when (depth > 2) $ enumerate $ do t1 <- gather $ build_tests (depth-1) lift . yield $ "(" ++ t1 ++ ")" lift . yield $ "[" ++ t1 ++ "]" t2 <- gather $ build_tests (depth-1) lift . yield $ "(" ++ t1 ++ "," ++ t2 ++ ")"
If we run this example with the input ["A", "B"] , the input "B" is never used, because source used only once inside each local .
main = do putStrLn "Depth 2" print =<< runBuildT ["A", "B"] (build_tests 2) putStrLn "Depth 3" print =<< runBuildT ["A", "B"] (build_tests 3)
The output for depths less than 4 is small enough to repeat here.
["A","a","[]","()"] Depth 3 ["A","a","[]","()","(A)","[A]","(A,A)","(A,a)","(A,[])","(A,())","(a)","[a]","(a,A)","(a,a)","(a,[])","(a,())","([])","[[]]","([],A)","([],a)","([],[])","([],())","(())","[()]","((),A)","((),a)","((),[])","((),())"]
It may be excessive.
I suspect you could mean source to get everything from the source.
source = gather P.cat yield = P.yield
If we use this as an example instead of getting a single element from the source, we will enumerate first local block and get the results return ing in ListT .
build_tests :: Monad m => Int -> BuildT [String] String String m () build_tests depth = do local $ enumerate $ do v <- source lift . yield $ v lift . yield $ (map toLower v) yield "[]" yield "()" when (depth > 2) $ enumerate $ do t1 <- gather $ build_tests (depth-1) lift . yield $ "(" ++ t1 ++ ")" lift . yield $ "[" ++ t1 ++ "]" t2 <- gather $ build_tests (depth-1) lift . yield $ "(" ++ t1 ++ "," ++ t2 ++ ")"
This uses both source values when we run the two-source example.
Depth 2 ["A","a","B","b","[]","()"] Depth 3 ["A","a","B","b","[]","()","(A)","[A]","(A,A)","(A,a)","(A,B)","(A,b)","(A,[])","(A,())","(a)","[a]","(a,A)","(a,a)","(a,B)","(a,b)","(a,[])","(a,())","(B)","[B]","(B,A)","(B,a)","(B,B)","(B,b)","(B,[])","(B,())","(b)","[b]","(b,A)","(b,a)","(b,B)","(b,b)","(b,[])","(b,())","([])","[[]]","([],A)","([],a)","([],B)","([],b)","([],[])","([],())","(())","[()]","((),A)","((),a)","((),B)","((),b)","((),[])","((),())"]
If you never get a single value from the source, you can simply use ListT (ReaderT [i] m) o . You might still need a proxy server to avoid clutter with mplus .