How to write a monad that prints “step I from N” for each statement in the monad?

I'm not even sure that this is possible in any monad; Does this violate the laws of the monad? But this is similar to what should be possible in some design. In particular, there is a way to get something that I can write something like

do someOp () someOtherOp () thirdOp () 

and he will print

 step 1 of 3 step 2 of 3 step 3 of 3 

Will it require a Haskell pattern or will the monad work? (And if a Haskell template is required, how to do this?)

+8
haskell monads template-haskell
source share
5 answers

I assume that you want the steps to be displayed automatically, without having to sprinkle your code with registration applications.

The problem with this is that they are too flexible: at any time, the "shape" of the rest of the calculation may depend on the values ​​obtained from the calculation itself. This is made explicit in the type (>>=) , which is ma -> (a -> mb) -> mb .

As a result, there is no fixed number N common steps that you may know before starting the calculation.

However, Haskell offers two other abstractions that trade in some power and monad flexibility for being able to do most of the “static” analysis in advance: applicative functions and arrows .

Applicative functors, at the same time extremely useful, perhaps too "weak" for your needs. You cannot write a function inside an applicative functor that, when applied to a value, displays that value on the console. This is explained in the article, “Idioms are oblivious, arrows are meticulous, monads are illegible,” which contains some educational examples of the limits of each abstraction (applicative functors are called “idioms” in this article.)

Arrows offer the best compromise between expressiveness and static analysis. The "shape" of arrow calculations is fixed in a static pipeline. The data obtained during the calculation can affect the effects later in the pipeline (for example, you can print the value obtained using the previous effect in the calculation), but do not change the shape of the pipeline or the number of steps.

So, if you could express your calculations with the Kleisli arrow (monad arrow), perhaps you could write some arrow transformer (not a monad transformer) that adds the ability to automatically log.

The arrows package offers a range of arrow transformers. I think StaticArrow could automatically track the total number of steps. But you still need to write some functions to actually notify messages.

Edit: Here is an example of how to count the number of steps in a calculation using the arrows:

 module Main where import Data.Monoid import Control.Monad import Control.Applicative import Control.Arrow import Control.Arrow.Transformer import Control.Arrow.Transformer.Static type SteppedIO ab = StaticArrow ((,) (Sum Int)) (Kleisli IO) ab step :: (a -> IO b) -> SteppedIO ab step cmd = wrap (Sum 1, Kleisli cmd) countSteps :: SteppedIO ab -> Int countSteps = getSum . fst . unwrap exec :: SteppedIO ab -> a -> IO b exec = runKleisli . snd . unwrap program :: SteppedIO () () program = step (\_ -> putStrLn "What is your name?") >>> step (\_ -> getLine) >>> step (putStrLn . mappend "Hello, ") main :: IO () main = do putStrLn $ "Number of steps: " ++ show (countSteps program) exec program () 

Please note that the effect of step 3 is affected by the value obtained in step 2. This cannot be done using applications.

We use the applicator (,) (Sum Int) required by StaticArrow to encode static information (here is just the number of steps).

Showing steps as you complete them will require a bit more work.

Change # 2 If we are dealing with a sequence of commands in which the effect does not depend on the value obtained by the previous effect, then we can avoid using the arrows and count the steps using only applicative functors

 module Main where import Data.Monoid import Control.Applicative import Data.Functor.Compose type SteppedIO a = Compose ((,) (Sum Int)) IO a step :: IO a -> SteppedIO a step cmd = Compose (Sum 1, cmd) countSteps :: SteppedIO a -> Int countSteps = getSum . fst . getCompose exec :: SteppedIO a -> IO a exec = snd . getCompose program :: SteppedIO () program = step (putStrLn "aaa") *> step (putStrLn "bbb") *> step (putStrLn "ccc") main :: IO () main = do putStrLn $ "Number of steps: " ++ show (countSteps program) exec program 

Data.Functor.Compose comes from the transformers package.

Change # 3 . The following code extends the previous Applicative step-counting solution using the pipes package for the actual notification. An arrow-based solution could be adapted in a similar way.

 module Main where import Data.Monoid import Control.Applicative import Control.Monad.State import Data.Functor.Compose import Pipes import Pipes.Lift type SteppedIO a = Compose ((,) (Sum Int)) (Producer () IO) a step :: IO a -> SteppedIO a step cmd = Compose (Sum 1, yield () *> lift cmd) countSteps :: SteppedIO a -> Int countSteps = getSum . fst . getCompose exec :: SteppedIO a -> Producer () IO a exec = snd . getCompose stepper :: MonadIO m => Int -> Consumer () ma stepper n = evalStateP 0 $ forever $ do await lift $ modify succ current <- lift get liftIO $ putStrLn $ "step " ++ show current ++ " of " ++ show n program :: SteppedIO () program = *** does not change relative to the previous example *** main :: IO () main = runEffect $ exec program >-> stepper (countSteps program) 
+10
source share

Although I think that the solution for shooting from Daniel Diaz is an ideal way to do this, there is a fairly simple one (which, as I see, it also indicates in the comments), as, for example, in your example there is no data between different function calls.

Remember that since Haskell is lazy, functions can do many things that require macros in other languages. In particular, there is no problem having a list of IO actions. (Absolutely safe too: because of cleanliness, they cannot “leave early” to Haskell!) Then you can simply take the length of this list as a general bill, alternate it with print statements and do it. All in the base language do not need TH!

 sequenceWithStepCount :: [IO()] -> IO() sequenceWithStepCount actions = go actions 0 where nTot = length actions go [] _ = putStrLn "Done!" go (act:remains) n = do putStrLn ("Step "++show n++" of "++show nTot) act go remains $ succ n 

Used as

 do sequenceWithStepCount [ someOp () , someOtherOp () , thirdOp () ] 
+5
source share

There are two ways to violate this law, depending on what you mean.

For example, if return to be considered a step, then you would have a violation, because the first law of the monad could not be kept:

 do x <- return /= fx fx 

Similarly, if abstracting two steps to another named function is considered to be deleting the step, then you also violate the laws of the monad, because the third law of the monad will not be retained:

 m' = do x <- m fx do y <- m' /= do x <- m gyy <- fx gy 

However, if you have commands to explicitly output the output of "step", then there is no violation. This is because return cannot produce any output at all, and a sequence of two commands will simply add their step outputs together. Here is an example:

 import Control.Monad.Trans.State import Control.Monad.Trans.Class (lift) step :: StateT Int IO () step = do n <- get lift $ putStrLn $ "Step " ++ show n put (n + 1) command1 = do command1' -- The command1 logic without the step behavior step command2 = do command2' step -- etc. 

Please note that I do not include the total number of steps. There is no way to access this information for the monad. For this, I recommend Daniel to answer, because Applicative is a great solution to this problem of determining the number of steps statically without a Haskell template.

+3
source share

There are many journal libraries.

If you are interested in Monad-Logger - here you are: Control.Monad.Logger

And in Hackage you can find other libraries

+1
source share

Use a monad transformer for a stack on WriterT that counts how many >> and >>= been applied to the main monad.

-one
source share

All Articles