I'm twisting my brain into knots trying to understand how to combine the State monad with Maybe.
Let's start with a concrete (and intentionally trivial/unnecessary) example in which we use a State monad to find the sum of a list of numbers:
import Control.Monad.State
list :: [Int]
list = [1,4,5,6,7,0,3,2,1]
adder :: Int
adder = evalState addState list
addState :: State [Int] Int
addState = do
ms <- get
case ms of
[] -> return 0
(x:xs) -> put xs >> fmap (+x) addState
Cool.
Now let's modify it so that it returns a Nothing if the list contains the number 0. In other words, evalState addState' list should return Nothing (since list contains a 0). I thought it might look something like this...
addState' :: State [Int] (Maybe Int)
addState' = do
ms <- get
case ms of
[] -> return (Just 0)
(0:xs) -> return Nothing
(x:xs) -> put xs >> fmap (fmap (+x)) addState'
...it works but I assume there's a better way to do this...
I've played around with StateT and MaybeT and I can't get them to work. I've looked at a couple of intros to Monad transformers but they either didn't touch on this particular combo (i.e., State + Maybe) or the examples were too complex for me to understand.
TL;DR: I'd appreciate if someone could show how to write this (admittedly trivial) piece of code using StateT and MaybeT (two examples). (I'm assuming it isn't possible to write this code without the use of transformers - is that incorrect?)
P.S. My understanding is that StateT is probably better suited for this example, but it would be helpful conceptually to see both examples, if not too much trouble.
Update: As pointed out by #Brenton Alker, my first version of the code above doesn't work because of simple typo (I was missing an apostrophe). In the interest of focusing the question on the use of StateT/MaybeT, I'm correcting the post above. Just wanted to include this note to give context to his post.
The type I would recommend using is:
StateT [Int] Maybe Int
A really simple way to use Maybe/MaybeT is to just call mzero whenever you want to fail and mplus whenever you want to recover from a failed computation. This works even if they are layered within other monad transformers.
Here's an example:
addState' :: StateT [Int] Maybe Int
addState' = do
ms <- get
case ms of
[] -> return 0
(0:xs) -> mzero
(x:xs) -> put xs >> fmap (fmap (+x)) addState
-- This requires generalizing the type of `addState` to:
addState :: Monad m => StateT [Int] m Int
Notice that I wrote that in such a way that I didn't use any Maybe-specific operations. In fact, if you let the compiler infer the type signature it will deduce this more general type instead:
addState' :: MonadPlus m => StateT [Int] m Int
This works because StateT has the following MonadPlus instance:
instance MonadPlus m => MonadPlus (StateT s m) where ...
And Maybe will type-check as an instance of MonadPlus, which is why the above code works when we specialize m to Maybe.
I believe your solution is basically correct, you just have a few minor issues.
Your recursive call to addState is missing the prime - ie. it should be addState' (I suspect this is just an issue in pasting the question, given the reported error)
You're asserting adder :: Int, but in the new version it should be adder :: Maybe Int - I think this is the type error you're getting.
Unfortunately, I don't have the resources to try a transformers version at the moment.
This example implements a simple Stack using MaybeT (State [Int]) Int.
We have a State Monad that holds s -> (a, s) being s :: [Int] (a Stack) and a :: Maybe Int (Just the element you get out/put into the Stack or Nothing when you try to pop something out of an empty stack).
-- -----------------------------------------
-- -----------------------------------------
import Control.Monad.Trans.State
import Control.Monad.Trans.Maybe
-- -----------------------------------------
-- -----------------------------------------
pop2 :: MaybeT (State [Int]) Int
pop2 = MaybeT . state $ \xx -> case xx of
[] -> (Nothing, [])
x:xs -> (Just x, xs)
push2 :: Int -> MaybeT (State [Int]) Int
push2 x = MaybeT . state $ \xs -> (Just x, x:xs)
-- -----------------------------------------
-- -----------------------------------------
dup2 = do
x <- pop2
push2 x
push2 x
-- -----------------------------------------
-- -----------------------------------------
main = do
print $ runState (runMaybeT dup2) [12, 34, 56]
print $ runState (runMaybeT dup2) []
When we run the program, we get:
(Just 12,[12,12,34,56])
(Nothing,[])
Let's review the types:
Originally,
MaybeT :: m (Maybe a) -> MaybeT m a (m a monad enclosing a Maybe a)
We use m == State [Int] and a == Int
Therefore
MaybeT :: (State [Int]) (Maybe Int) -> MaybeT (State [Int]) Int
and
runMaybeT :: MaybeT m a -> m (Maybe a) == MaybeT (State [Int]) Int -> (State [Int]) (Maybe Int) (runMaybeT pulls out what MaybeT encloses).
and
runState of (State [Int]) (Maybe Int) == [Int] -> ((Maybe Int), [Int])
(runState pulls out what (State [Int]) (Maybe Int) encloses).
Related
I have function:
step :: forall m . (MonadState IntCodeState m) => m (Maybe ())
When I use do notation in the body of the function, it uses m as the monad. As you might expect, I actually want it to be using m Maybe. But, it doesn't understand that m Maybe is a monad. How do I express that to Haskell?
EDIT: This may be slightly malformed at the moment. The concrete type should be
StateT IntCodeState Maybe (), but I'm trying not to declare the concrete type, so the question is: how do I declare that?
EDIT 2: Another attempt: I've got some functions that look like this:
getValueIndex :: (MonadState IntCodeState m) => Int -> m (Maybe Int)
Here, I'm working on the level of the state monad. However, I now want to be able to act "as if" m Maybe was the monad. I was hoping this was simple but I can't figure out a way of expressing it. The code I want to write looks like this
step :: forall m . (MonadState IntCodeState m) => m (Maybe ())
step = do
full <- opCode
let len = length (snd full) + 1
process full <* (next += len)
But opCode returns a m (Maybe a) and I want full to be an a
But opCode returns a m (Maybe a) and I want full to be an a
Looks like you want to use the MaybeT monad transformer, which is m (Maybe a) under the hood, with its Monad instance doing what you need:
The MaybeT monad transformer extends a monad with the ability to exit the computation without returning a value.
A sequence of actions produces a value only if all the actions in the sequence do. If one exits, the rest of the sequence is skipped and the composite action exits.
Here are the types:
MaybeT :: m (Maybe a) -> MaybeT m a
runMaybeT :: MaybeT m a -> m (Maybe a)
This will also be helpful, specialised from MonadTrans:
lift :: m a -> MaybeT m a
So in your case:
step :: forall m . (MonadState IntCodeState m) => m (Maybe ())
step = runMaybeT $ do
full <- MaybeT opCode -- :: MaybeT opCode :: MaybeT m a, full :: a
let len = length (snd full) + 1
lift $ process full <* (next += len)
I've assumed process returns an m () and used lift to change it into MaybeT m ().
Working in an IO computation I ended up with a staircase of case mbValue of …s and figured out that I should use the Maybe monad to simplify the code. Since it's within an IO computation and I need to get IO values, I used the MaybeT monad transformer so that I can lift IO computation into Maybe.
Now, I have always thought about values being “stripped” of their Maybeness after an values <- mbValue inside a Maybe computation, but this turns out to be too simple of a heuristic here.
As highlighted below, when using a Maybe a value as an a (here by passing it to read), it fails to type check:
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (runMaybeT)
lol :: IO (Maybe Int)
lol = return (Just 3)
lal :: IO (Maybe String)
lal = return (Just "8")
foo :: IO (Maybe Bool)
foo = do
b <- runMaybeT $ do
x <- lift lol
y <- lift lal
return (x < (read y))
return b ^-- Couldn't match type ‘Maybe String’ with ‘String’
main = foo >>= print
If I put a typed hole in for return (x < (read y)), I see that it expects a Bool, which makes sense, but also that the current bindings include
|| y :: Data.Maybe.Maybe GHC.Base.String
|| (bound at /private/tmp/test.hs:14:5)
|| x :: Data.Maybe.Maybe GHC.Types.Int
|| (bound at /private/tmp/test.hs:13:5)
I.e., y is a Maybe String. This of course explains the error, but I'm left confused. Where is my understanding wrong, and how can I fix this error?
In short: Replace lift by the MaybeT constructor.
Note that
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
and
lift :: (MonadTrans t, Monad m) => m a -> t m a
Your use of lift in
x <- lift lol
is at the type
lift :: IO (Maybe Int) -> MaybeT IO (Maybe Int)
That's why x will be a Maybe Int again. The lift adds a fresh MaybeT layer that is independent of the Maybe occurrence you already have.
But
MaybeT :: m (Maybe a) -> MaybeT m a
instead as in
x <- MaybeT lol
will be used at type
MaybeT :: IO (Maybe a) -> MaybeT IO a
and do the right thing.
When specialized to MaybeT, lift :: Monad m => m a -> MaybeT m a. Since lol :: IO (Maybe Int), m is IO and a is Maybe Int, therefore lift lol :: MaybeT IO (Maybe Int).
IO (Maybe a) is just the value contained within a MaybeT IO a newtype wrapper, so there's no need to lift it; instead use the MaybeT constructor, for example as in MaybeT lol.
But this is not how people tend to use monad transformers. Instead, just use MaybeT values and lift as needed:
import Control.Monad
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (runMaybeT, MaybeT)
lol :: MaybeT IO Int
lol = return 3
lal :: MaybeT IO String
lal = return "8"
foo :: IO (Maybe Bool)
foo =
runMaybeT $ do
x <- lol
y <- lal
_ <- lift getLine -- lift (IO String) to MaybeT IO String
_ <- return 100 -- lift any pure value
_ <- mzero -- use the MonadPlus instance to get a lifted Nothing.
return (x < (read y))
main = foo >>= print
If we have the following two functions, add and subtract, it is simple to chain them to run a series of calculations on an input:
add :: Int -> State Int ()
add n = state $ \x -> ((),x+n)
subtract :: Int -> State Int ()
subtract n = state $ \x -> ((),x-n)
manyOperations :: State Int ()
manyOperations = do
add 2
subtract 3
add 5
--etc
result = execState manyOperations 5
--result is 9
If we want to print out the state after each calculation is done, we use the StateT monad transformer:
add :: Int -> StateT Int IO ()
add n = StateT $ \x -> print (x+n) >> return ((),x+n)
subtract :: Int -> StateT Int IO ()
subtract n = StateT $ \x -> print (x-n) >> return ((),x-n)
manyOperations :: StateT Int IO ()
manyOperations = do
add 2
subtract 3
add 5
main = runStateT manyOperations 5
-- prints 7, then 4, then 9
Is it possible to replicate this "combined computation and printing" without StateT or any custom datatypes?
As far as I know it's possible to do all the processes that MaybeT IO a can do using IO (Maybe a), but it seems like that's because it's just nested monads. On the other hand, StateT might not have an alternative because s -> (a,s) is fundamentally different to s -> m (a,s)
I can only see two directions the code could go: using State Int (IO ()) or IO (State Int ()), but both of these seem nonsensical given the implementation of StateT
I believe it is impossible. Am I correct?
Note: I know this is completely impractical, but I couldn't find any solution after some hours of work, which means I'm correct or my skills aren't enough for the task.
Of course you can do all the plumbing yourself. Monads don't add anything new to Haskell, they just enable a ton of code reuse and boilerplate reduction. So anything you can do with a monad, you can laborously do by hand.
manyOperations :: Int -> IO ()
manyOperations n0 = do
let n1 = n0 + 2
print n1
let n2 = n1 - 3
print n2
let n3 = n2 + 5
print n3
If the amount of repetition in the above function is too much for you (it is for me!) you could try to reduce it with a 'combined computation and printing' function but at this point you're bending over backwards to avoid StateT.
-- a bad function, never write something like this!
printAndCompute :: a -> (a -> b) -> IO b
printAndCompute a f = let b = f a in print b >> return b
-- seriously, don't do this!
manyOperations n0 = do
n1 <- printAndCompute (+2) n0
n2 <- printAndCompute (-3) n1
n3 <- printAndCompute (+5) n2
return ()
I'm not sure if this is quite what you're looking for, but you could define an operation which takes a stateful operation that you alread have, and prints out the state after performing the operation -
withPrint :: (Show s) => State s a -> StateT s IO a
withPrint operation = do
s <- get
let (a, t) = runState operation s
liftIO (print t)
put t
return a
and then do
manyOperations :: StateT Int IO ()
manyOperations = do
withPrint (add 2)
withPrint (subtract 3)
withPrint (add 5)
-- etc
This doesn't avoid using StateT but it abstracts out the conversion of your regular, non-IO state operations into IO-ful state operations so you don't have to worry about the details (aside from adding withPrint when you want the state to be printed.
If you don't want the state printed for a particular operation, you could define
withoutPrint :: State s a -> StateT s IO a
withoutPrint operation = do
s <- get
let (a, t) = runState operation s
put t
return a
which is actually equivalent to
import Control.Monad.Morph
withoutPrint :: State s a -> StateT s IO a
withoutPrint = hoist (\(Identity a) -> return a)
using hoist from Control.Monad.Morph to transform the monad underlying StateT from Identity to IO.
You can completely avoid those data types by using s -> IO (a, s) directly, substituting for a and s appropriately. It definitely won't be as nice though.
It would look something like this:
-- This makes StateIO s act as a shorthand for s -> IO (a, s)
type StateIO s a = s -> IO (a, s)
add :: Int -> StateIO Int ()
add n = \x -> print (x+n) >> return ((),x+n)
sub :: Int -> StateIO Int ()
sub n = \x -> print (x-n) >> return ((),x-n)
manyOperations :: StateIO Int ()
manyOperations = -- Using the definition of (>>=) for StateT
\s1 -> do -- and removing a lambda that is immediately applied
(a, s2) <- add 2 s1
(a, s3) <- sub 3 s2
add 5 s3
result :: IO ((), Int)
result = manyOperations 5
The state has to be explicitly threaded through all the operations. This is a major gain we get from using the StateT datatype: the (>>=) method of its Monad instance does all this for us! We can see from this example, though, that there is nothing magical going on in StateT. It's just a very nice way of abstracting out the threading of state.
Also, the relationship between StateT and State is the opposite of the relationship between MaybeT and Maybe: State is defined in terms of StateT. We can see that fact expressed in this type synonym:
type State s = StateT s Identity
It is a transformer over the Identity type (which has the trivial Monad instance).
I'n looking at the State Monad's put and get:
ghci> :t get
get :: MonadState s m => m s
ghci> :t runState
runState :: State s a -> s -> (a, s)
ghci> runState get [1,2,3]
([1,2,3],[1,2,3])
In get's type signature: MonadState s m => m s, how does [1,2,3] have a type of MonadState s m? It's not clear to me what the types of s and m are.
Also, can you please say more as to how to use put?
ghci> :t put
put :: MonadState s m => s -> m ()
Overall, it seems that I don't understand what MonadState s m is. Could you please explain with put and get examples?
MonadState s m is a typeclass constraint, not a type. The signature:
get :: MonadState s m => m s
Says that for some monad m storing some state of type s, get is an action in m that returns a value of type s. This is pretty abstract, so let’s make it more concrete with the less-overloaded version of State from transformers:
get :: State s s
put :: s -> State s ()
Now say we want to use State to keep a simple counter. Let’s use execState instead of runState so we can just pay attention to the final value of the state. We can get the value of the counter:
> execState get 0
0
We can set the value of the counter using put:
> execState (put 1) 0
1
We can set the state multiple times:
> execState (do put 1; put 2) 0
2
And we can modify the state based on its current value:
> execState (do x <- get; put (x + 1)) 0
1
This combination of get and put is common enough to have its own name, modify:
> execState (do modify (+ 1)) 0
1
> execState (do modify (+ 2); modify (* 5)) 0
10
MonadState is the class of types that are monads with state. State is an instance of that class:
instance MonadState s (State s) where
get = Control.Monad.Trans.State.get
put = Control.Monad.Trans.State.put
So are StateT (the state monad transformer, which adds state to another monad) and various others. This overloading was introduced so that if you’re using a stack of monad transformers, you don’t need to explicitly lift operations between different transformers. If you’re not doing that, you can use the simpler operations from transformers.
Here’s another example of how to use State to encapsulate a map of variables with Data.Map:
import Control.Monad.Trans.State
import qualified Data.Map as M
action = do
modify (M.insert "x" 2) -- x = 2
modify (M.insert "y" 3) -- y = 3
x <- gets (M.! "x")
y <- gets (M.! "y")
modify (M.insert "z" (x + y)) -- z = x + y
modify (M.adjust (+ 2) "z") -- z += 2
gets (M.! "z") -- return z
main = do
let (result, vars) = execState action M.empty
putStr "Result: "
print result
putStr "Vars: "
print vars
In get's type signature: MonadState s m => m s, how does [1,2,3] have
a type of MonadState s m? It's not clear to me what the types of s and
m are.
ghci> runState get [1,2,3]
The function runState takes two arguments: the first is the State action to run, and the second is the initial state. So, since the initial state is [1,2,3] which is a list of integers (*), the state type s is just [Integer].
(*) Actually, [1,2,3] :: Num a => [a] before GHCi defaults it, but for simplicity's sake let's use [Integer] as GHCi does.
Hence, we see that runState is specialized to
runState :: State [Integer] a -> [Integer] -> (a, [Integer])
Now, about the first argument:
get :: MonadState s m => m s
We must have m s = State s a because we are passing it to runState which requires such type. Hence:
runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState s m => m s
with m s = State [Integer] a
The latter equation can be simplified as follows:
runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState s m => m s
with m = State [Integer]
and s = a
Substituting s:
runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState a m => m a
with m = State [Integer]
Substituting m:
runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState a (State [Integer]) => State [Integer] a
Now, the constraint MonadState a (State [Integer]) is satisfied only when a = [Integer]. This is tricky to see, since the MonasState type class exploits a functional dependency to enforce that every monad in that class has only one related state type. This is also made more complex from State being a wrapper around StateT. Anyway, we get:
runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState a (State [Integer]) => State [Integer] a
with a = [Integer]
So,
runState :: State [Integer] [Integer] -> [Integer] -> ([Integer], [Integer])
get :: MonadState [Integer] (State [Integer]) => State [Integer] [Integer]
And since the constraint is satisfied:
runState :: State [Integer] [Integer] -> [Integer] -> ([Integer], [Integer])
get :: State [Integer] [Integer]
And now we can see the involved ground types.
My goal is to create a function that uses the list monad inside either a ReaderT WriterT stack, or a RWS stack. More generally, how do I use the list monad inside mtl typeclasses such as MonadReader, MonadWriter?
Why am I trying to do this? This problem is an exercise in Beginning Haskell. It asks me to "use functionality from both MonadReader and MonadWriter wrapping the base list monad. To check that the function is general, use two different monads to [test] the requested functionality: ReaderT r (WriterT w []) a and RWST r w s m a" So the book implies this is possible.
I can't figure out how to 'tell' the compiler to use the list monad. If I use ask >>= lift or ask >>= lift . lift I can get either the 2 level stack (RWST []) or 3 level stack (ReaderT WriterT []) to work, but not both.
The focus of my question:
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
do (s0, e0) <- ask >>= lift
guard $ s0 == start
tell [start]
pathImplicitStack' e0 end
Additionally, I'd like to know how to type the function. My best attempt so far looks something like pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m () I know this isn't right, the list monad is probably missing. Also, I think MonadPlus might be useful in the type signature, but I'm not quite sure.
This line: do (s0, e0) <- ask >>= lift is the one giving me trouble. I've tried 0, 1, and 2 lifts without success. I'd like to ask for a [(Int, Int)] and then use the list monad to process just a (Int, Int) (and let the list monad try all possibilities for me).
As part of the exercise, I need to be able to call pathImplicitStack' with both of these functions (or very similar functions):
pathImplicitRW :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRW edges start end = execWriterT rdr
where rdr = runReaderT (pathImplicitStack' start end) edges :: WriterT [Int] [] ()
pathImplicitRWS :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRWS edges start end = map snd exec
where exec = execRWST (pathImplicitStack' start end) edges ()
This is related to my previous question: How do I use list monad inside of ReaderT?
The whole file for easy testing:
{-# LANGUAGE FlexibleContexts #-}
module Foo where
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.RWS
graph1 :: [(Int, Int)]
graph1 = [(2013,501),(2013,1004),(501,2558),(1004,2558)]
pathImplicitRW :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRW edges start end = execWriterT rdr
where rdr = runReaderT (pathImplicitStack' start end) edges :: WriterT [Int] [] ()
pathImplicitRWS :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRWS edges start end = map snd exec
where exec = execRWST (pathImplicitStack' start end) edges ()
pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> [m ()]
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
do (s0, e0) <- ask >>= lift
guard $ s0 == start
tell [start]
pathImplicitStack' e0 end
edit
Based on John L's feedback I tried
pathImplicitStack' :: (MonadReader [(Int, Int)] (t []), MonadWriter [Int] (t []), MonadPlus (t []), MonadTrans t) => Int -> Int -> t [] ()
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
do (s0, e0) <- ask >>= lift
guard $ s0 == start
tell [start]
pathImplicitStack' e0 end
but as he pointed out, it can only be used with one monad transformer to wrap the list monad, i.e. RSWT, and is not usable with ReaderT WriterT. So this is not the solution I am looking for.
So, the basic problem with doing this within the requirements of the question is that there is no MTL library function for lifting from a list monad that may be arbitrary levels down. However, you can "cheat" a bit: The MonadPlus instance for the combined Monad is inherited from the underlying list monad regardless of the depth, and you can use it to generate the needed action:
do (s0, e0) <- ask >>= msum . map return
There is then also an error in the type signature, which needs to be changed to:
pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m ()
EDIT: Actually on second thought this is not actually cheating. It's just using the MonadPlus API for chaining alternative actions instead of using the underlying list monad directly.
I think the difficulty here is that you're mixing up layers of the monad. Looking at
pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> [m ()]
This function returns a list of m () computations, however the ask >>= lift (and your earlier question) are presuming that List is the base monad upon which you stack extra transformers. If you want to use List as the base monad, you'll need to change the type of pathImplicitStack'
pathImplicitStack' :: (MonadReader [(Int, Int)] (t []), MonadWriter [Int] (t []), MonadPlus (t [])) => Int -> Int -> t [] ()
But even this isn't quite general enough, because this only allows adding a single transformer on top of the List. You could use a type-operator library to combine two monad transformers into a single transformer, but that seems a bit complicated for this.
Here is one option: use Identity as the base monad and perform your list operations outside that monad stack. (warning, all code untested, it may not even compile)
pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m ()
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
do (s0, e0) <- ask >>= lift
edges <- filter ((== start) . fst) <$> ask
let m (s0,e0) = tell [s0] >> pathImplicitStack' e0 end
mapM_ m edges
There is another option. You can use ListT or LogicT as the outer transformer, letting you use this function (or something like it):
pathImplicitStack'2 :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m) => Int -> Int -> ListT m ()
pathImplicitStack'2 start end | start == end = tell [end]
pathImplicitStack'2 start end =
do (s0, e0) <- ask
guard $ s0 == start
tell [start]
pathImplicitStack'2 e0 end
-- if you have MonadReader/MonadWriter instances for ListT in scope, I think this will
-- work. But if they aren't available, you will need to use `lift ask` and
-- `lift (tell ...)`
I would almost certainly choose the first approach.