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 <[email protected]> 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 <[email protected] 
>>>> <mailto:[email protected]>> 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 [email protected] 
>>>> <mailto:[email protected]>.
>>>> To post to this group, send email to [email protected] 
>>>> <mailto:[email protected]>.
>>> 
>>> -- 
>>> 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 [email protected] 
>>> <mailto:[email protected]>.
>>> To post to this group, send email to [email protected] 
>>> <mailto:[email protected]>.
>> 
> 
> 
> -- 
> 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 [email protected] 
> <mailto:[email protected]>.
> To post to this group, send email to [email protected] 
> <mailto:[email protected]>.

-- 
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 [email protected].
To post to this group, send email to [email protected].

Reply via email to