One tip I would give is to not use `mtl` type classes when trying to reason 
about the code's behavior.  Just use the `transformers` library directly and 
specify a concrete monad transformer stack.  That will make it easier to 
understand what is going on.

> On Apr 9, 2017, at 8:25 AM, Martyn J Pearce <martyn.j.pea...@gmail.com> wrote:
> 
> 
> I'm pleased to say that I spoke too soon.  The second stanza does work, 
> though I now need to work out why.
> 
>   sys'' :: (MonadReader (CmdSpec -> m b) (t (Producer [CmdSpec] m)),
>             MonadTrans t, Monad m) =>
>            CmdSpec -> t (Producer [CmdSpec] m) b
>   sys'' c = ask >>= lift . sys c
> 
>   runEffect (for (runReaderT (sys'' g2 >> sys'' g1) callCommand)
>                  (lift . mapM_ putStrLn))
> 
> 
> I think I need to think carefully about how MonadTrans works, and maybe even 
> try a little equational reasoning to get my head around that one.
> But, I have something that works, which obviously means that I can work from 
> there.  And for that I am grateful.
> 
> Many thanks,
> Martyn.
> 
> On 09/04/17 15:25, Martyn J Pearce wrote:
>> Thanks very much for the pointers, Gabriel.  I think I'm following it, and 
>> the nub of the matter seems to be that within sys' I am 'return'ing IO 
>> action rather than 'lift'ing it.
>> 
>> However, I am struggling to get either proposed solution (or any near 
>> variant that I can see) to work.
>> 
>>   λ> :t \c -> lift ask >>= sys c
>>   \c -> lift ask >>= sys c :: MonadReader (CmdSpec -> m b) m => CmdSpec -> 
>> Pipes.Internal.Proxy Pipes.Internal.X () () [CmdSpec] m b
>> 
>> In this formulation, the Monad m is both the reader monad and the result of 
>> the function read.  This makes it impossible to instantiate (I think) 
>> because of the recursive type.
>> 
>>   λ> :t \c -> ask >>= lift . sys c
>>   \c -> ask >>= lift . sys c
>>     :: (MonadReader (CmdSpec -> m b) (t (Pipes.Internal.Proxy 
>> Pipes.Internal.X () () [CmdSpec] m)), MonadTrans t, Monad m) =>
>>        CmdSpec -> t (Pipes.Internal.Proxy Pipes.Internal.X () () [CmdSpec] 
>> m) b
>> 
>> That Just Looks Wrong(TM), and still has m both within and without the 
>> reader.
>> 
>> I feel that the 'solution' is very near by, but I'm not seeing it (despite 
>> experimentation).
>> 
>> I appreciate that this isn't really a Pipes problem.  If there's a better 
>> place I should ask this, that's cool.  Of course, if you (or anybody else 
>> here) is able/willing to point me in the right direction, that would be 
>> appreciated.
>> 
>> Cheers,
>> 
>> Martyn.
>> 
>> 
>> On 08/04/17 05:27, Gabriel Gonzalez wrote:
>>> When in doubt, you can use equational reasoning to figure out what is going 
>>> on.
>>> 
>>> In this case, let's use the first example, which is:
>>> 
>>>     runEffect $ for (runReader (sys' g1 >> sys' g2) callCommand)
>>>                     (lift . mapM_ putStrLn)
>>> 
>>> If we inline the definition of `sys'` we get:
>>> 
>>>     runEffect $ for (runReader ((ask >>= return . sys g1) >> (ask >>= 
>>> return . sys g2)) callCommand)
>>>                     (lift . mapM_ putStrLn)
>>> 
>>> Now let's focus on this subexpression:
>>> 
>>>     (ask >>= return . sys g1) >> (ask >>= return . sys g2)
>>> 
>>> ... which is equivalent to:
>>> 
>>>     (ask >>= return . sys g1) >>= \_ -> (ask >>= return . sys g2)
>>> 
>>> The associativity monad law says that:
>>> 
>>>     (m >>= f) >>= g = m >>= \x -> (f x >>= g)
>>> 
>>> ... where in this case:
>>> 
>>>     m = ask
>>>     f = return . sys g1
>>>     g = \_ -> (ask >>= return . sys g2)
>>> 
>>> ... which means that we can transform it to:
>>> 
>>>     ask >>= \x -> ((return . sys g1) x >>= \_ -> (ask >>= return . sys g2))
>>> 
>>> ... and according to the definition of function composition that is the 
>>> same as:
>>> 
>>>     ask >>= \x -> (return (sys g1 x) >>= \_ -> (ask >>= return . sys g2))
>>> 
>>> Now we can use the left identity monad law which states that:
>>> 
>>>     return a >>= f = f a
>>> 
>>> ... where in this case:
>>> 
>>>     a = sys g1 x
>>>     f = \_ -> (ask >>= return . sys g2)
>>> 
>>> ... which means that we can transform it into:
>>> 
>>>     ask >>= \x -> (\_ -> (ask >>= return . sys g2)) (sys g1 x)
>>> 
>>> ... which further simplifies to:
>>> 
>>>     ask >>= \x -> ask >>= return . sys g2
>>> 
>>> ... which further simplifies to:
>>> 
>>>     ask >> ask >>= return . sys g2
>>> 
>>> ... which shows that the `g1` is completely discarded.
>>> 
>>> Notice that this has nothing to do with `pipes`.  This is confined entirely 
>>> to the `Reader`-related code.
>>> 
>>> It might be more clear if we use do notation.  The original Reader 
>>> expression in do notation would be:
>>> 
>>>     do _ <- do x <- ask
>>>                return (sys g1 x)
>>>        y <- ask
>>>        return (sys g2 y)
>>> 
>>> ... and the monad laws say that it is equivalent to:
>>> 
>>>     do x <- ask
>>>        _ <- return (sys g1 x)
>>>        y <- ask
>>>        return (sys g2 y)
>>> 
>>> The `sys g1 x` that you are `return`ing is being discarded by the empty 
>>> assignment: `_ <-`.
>>> 
>>> What I think you probably meant to do was to run the `sys g1 x` command 
>>> instead of `return`ing it as an inert value and then discarding it.  In 
>>> other words, I think you wanted something like this:
>>> 
>>>     do x <- ask
>>>        _ <- lift (sys g1 x)
>>>        y <- ask
>>>        lift (sys g2 y)
>>> 
>>> ... or this:
>>> 
>>>     do x <- lift ask
>>>        _ <- sys g1 x
>>>        y <- lift ask
>>>        sys g2 y
>>> 
>>> ... depending on which order you nest your monad transformers.
>>> 
>>>> On Apr 7, 2017, at 8:20 AM, Martyn J Pearce <martyn.j.pea...@gmail.com 
>>>> <mailto:martyn.j.pea...@gmail.com>> wrote:
>>>> 
>>>> I have a slightly simpler version (and evidence that I am trying to solve 
>>>> this myself, or at least, am stumbling around in the dark) that stands 
>>>> alone (with mtl, pipes & process).
>>>> Still, somewhere, I am losing the first grep.
>>>> The second example shows that if I lose the MonadReader, all is well.  So 
>>>> I considered re-ordering the monad evaluation, as you will see - but to no 
>>>> avail.
>>>> 
>>>> {-# LANGUAGE FlexibleContexts #-}
>>>> 
>>>> -- base --------------------------------
>>>> import Control.Monad         ( Monad, (>>), (>>=), mapM_, return )
>>>> import Data.Function         ( (.), ($) )
>>>> import Data.String           ( String )
>>>> import System.IO             ( IO, putStrLn )
>>>> 
>>>> -- mtl ---------------------------------
>>>> import Control.Monad.Reader  ( MonadReader, ask, runReader )
>>>> 
>>>> -- pipes -------------------------------
>>>> import Pipes                 ( Producer, for, lift, runEffect, yield )
>>>> 
>>>> -- process -----------------------------
>>>> import System.Process        ( callCommand )
>>>> 
>>>> -------------------------------------------------------------------------------
>>>> 
>>>> type CmdSpec = String
>>>> 
>>>> -- | "log" cmd, then "run" cmd with exec
>>>> sys :: Monad m => CmdSpec -> (CmdSpec -> m b) -> Producer [CmdSpec] m b
>>>> sys cmd exec = do
>>>>  yield [cmd]
>>>>  lift $ exec cmd
>>>> 
>>>> -- | log & run cmd, in the context of an executor
>>>> sys' :: (MonadReader (CmdSpec -> m b) n, Monad m) =>
>>>>        CmdSpec -> n (Producer [CmdSpec] m b)
>>>> sys' c = ask >>= return . sys c
>>>> 
>>>> g1 :: CmdSpec
>>>> g1 = "/bin/grep root /etc/passwd"
>>>> 
>>>> g2 :: CmdSpec
>>>> g2 = "/bin/grep Ubuntu /etc/lsb-release"
>>>> 
>>>> -- what happens to the first grep?
>>>> main :: IO ()
>>>> main = do
>>>>  runEffect $ for (runReader (sys' g1 >> sys' g2) callCommand)
>>>>                  (lift . mapM_ putStrLn)
>>>> 
>>>>  putStrLn "----"
>>>> 
>>>>  runEffect $ for (sys g1 callCommand >> sys g2 callCommand)
>>>>                  (lift . mapM_ putStrLn)
>>>> 
>>>>  putStrLn "----"
>>>> 
>>>>  runEffect $ runReader (fmap ( \ f -> for f (lift . mapM_ putStrLn))
>>>>                              (sys' g1 >> sys' g2))
>>>>                        callCommand
>>>> 
>>>>  putStrLn "----"
>>>> 
>>>>  runReader (runEffect <$> fmap ( \ f -> for f (lift . mapM_ putStrLn))
>>>>                                (sys' g1 >> sys' g2))
>>>>            callCommand
>>>> 
>>>> On 07/04/17 13:14, Martyn J Pearce wrote:
>>>>> Dear Pipers (Pipe Smokers?),
>>>>> 
>>>>> Please accept my apologies for a lengthy example for what is undoubtedly 
>>>>> a simple problem, but I'm struggling to know how best to simplify.
>>>>> 
>>>>> I'm writing a little command-execution package.  It's primarily for my 
>>>>> own learning, so don't worry about "couldn't I use package X...".
>>>>> 
>>>>> I have a 'systemx' function, that creates a command that will be run with 
>>>>> stdout/stderr inherited from the process:
>>>>> 
>>>>>  systemx :: (MonadError ExecError μ, MonadReader (ProcExecCtxt3 μ) ρ) => 
>>>>> CmdSpec -> ρ (Producer [CmdSpec] μ ())
>>>>> 
>>>>> A CmdSpec is a command specification - that is, an executable name & some 
>>>>> arguments.
>>>>> ProcExecCtxt3 holds an 'executor' - a function to 'execute' the command; 
>>>>> in normal circumstances, this uses System.Process et al, but it's 
>>>>> replaceable so that I could
>>>>> mock the command.  Hence the free typevar μ, which is MonadIO in normal 
>>>>> circumstances but could be a non-IO monad for mock purposes. The 
>>>>> ExecError is thrown if the command returns non-zero.
>>>>> The producer is to "log" the commands run - when mocking, I can use a 
>>>>> MonadWriter to collect the list, and use that for testing.
>>>>> 
>>>>> Now, this all works fine in simple form, where runCtxt3 is mechanics for 
>>>>> executing via System.Process:
>>>>> 
>>>>> cmds1 :: (MonadError ExecError μ, MonadReader (ProcExecCtxt3 μ) ρ) => ρ 
>>>>> (Producer [CmdSpec] μ ())
>>>>> cmds1 = systemx (CmdSpec grep ["root", "/etc/passwd"])
>>>>> 
>>>>> cmds1' :: (MonadError ExecError μ, MonadReader (ProcExecCtxt3 μ) ρ) => ρ 
>>>>> (Producer [CmdSpec] μ ())
>>>>> cmds1' = systemx (CmdSpec grep ["foo", "/etc/motd"])
>>>>> 
>>>>> λ> :t runCtxt3
>>>>> runCtxt3 :: MonadIO μ => ProcExecCtxt3 μ
>>>>> 
>>>>> λ> runExceptT $ runEffect $ for (runReader (cmds1') runCtxt3) (mapM_ 
>>>>> warnCmd)
>>>>> CMD: /bin/grep foo /etc/motd
>>>>> /bin/grep: /etc/motd: No such file or directory
>>>>> Left (ExecError (ExitVal 2))
>>>>> 
>>>>> λ> runExceptT $ runEffect $ for (runReader cmds1 runCtxt3) (mapM_ warnCmd)
>>>>> CMD: /bin/grep root /etc/passwd
>>>>> root:x:0:0:root:/root:/bin/bash
>>>>> Right ()
>>>>> 
>>>>> BUT!  If I attempt to execute two commands, only the last one gets run:
>>>>> 
>>>>> cmds2 :: (MonadError ExecError μ, MonadReader (ProcExecCtxt3 μ) ρ) => ρ 
>>>>> (Producer [CmdSpec] μ ())
>>>>> cmds2 = do
>>>>>  systemx grepIt
>>>>>  systemx grepIt2
>>>>> 
>>>>> This is baffling me.  I would suspect laziness, but that we're clearly 
>>>>> running within MonadIO, that doesn't apply, right?
>>>>> 
>>>>> I'm sure I'm being a bit dim, but any pointers would be gratefully 
>>>>> received.
>>>>> 
>>>>> Yours,
>>>>> Martyn.
>>>>> 
>>>> 
>>>> -- 
>>>> You received this message because you are subscribed to the Google Groups 
>>>> "Haskell Pipes" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send an 
>>>> email to haskell-pipes+unsubscr...@googlegroups.com 
>>>> <mailto:haskell-pipes+unsubscr...@googlegroups.com>.
>>>> To post to this group, send email to haskell-pipes@googlegroups.com 
>>>> <mailto:haskell-pipes@googlegroups.com>.
>>> 
>>> -- 
>>> You received this message because you are subscribed to the Google Groups 
>>> "Haskell Pipes" group.
>>> To unsubscribe from this group and stop receiving emails from it, send an 
>>> email to haskell-pipes+unsubscr...@googlegroups.com 
>>> <mailto:haskell-pipes+unsubscr...@googlegroups.com>.
>>> To post to this group, send email to haskell-pipes@googlegroups.com 
>>> <mailto:haskell-pipes@googlegroups.com>.
>> 
> 
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "Haskell Pipes" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to haskell-pipes+unsubscr...@googlegroups.com 
> <mailto:haskell-pipes+unsubscr...@googlegroups.com>.
> To post to this group, send email to haskell-pipes@googlegroups.com 
> <mailto:haskell-pipes@googlegroups.com>.

-- 
You received this message because you are subscribed to the Google Groups 
"Haskell Pipes" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to haskell-pipes+unsubscr...@googlegroups.com.
To post to this group, send email to haskell-pipes@googlegroups.com.

Reply via email to