I\'m not even sure this is possible in any kind of monad; does it violate monad laws? But it seems like something that should be possible in some kind of construct or other. S
There are two ways that this might violate the laws, depending on what you mean.
For example, if return
were to count as a step, then you'd have a violation because the first monad law would not hold:
do x <- return /= f x
f x
Similarly, if abstracting out two steps into another named function counts as removing a step, then you also violate the monad laws, because the third monad law would not hold:
m' = do x <- m
f x
do y <- m' /= do x <- m
g y y <- f x
g y
However, if you have commands explicitly emit the "step" output, then there is no violation. This is because return
could then not emit any output at all, and sequencing two commands would just add their step outputs together. Here's 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.
Note that I don't include the total number of steps. There's no way to have access to that information for a monad. For that I recommend Daniel's answer, because Applicative
s are an excellent solution to this problem of determining the number of steps statically without any Template Haskell.