Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my MaybeT (State <type>) () ignore state changes?

Short version: when I use runMaybeT and then runState on a monad of type MaybeT (State <type>) (), it looks like no state changes occur even though the Maybe result is equal to Just (). Why?

Full version: I'm writing a program to solve the Towers of Hanoi. I represent the solution as a list of State monads that, when sequenced, manipulate an initial Towers state:

data Towers = Towers [Int] [Int] [Int]
    deriving (Show)
type Move = State Towers ()

towerMoves :: Int -> Rod -> Rod -> [Move]
towerMoves 1 r1 r2 = [pop r1 >>= push r2]
towerMoves n r1 r2 = topToTemp ++ (towerMoves 1 r1 r2) ++ topToFinal
  where
    r3 = other r1 r2
    topToTemp = towerMoves (n - 1) r1 r3
    topToFinal = towerMoves (n - 1) r3 r2

moves = towerMoves 5 First Third
initTowers = Towers [1,2,3,4,5] [] []

main = print $ snd $ runState (sequence_ moves) initTowers

Thus far, this program produces the correct output:

Towers [] [] [1,2,3,4,5]

Then, I wanted to verify that the program respected the rules of the puzzle, namely that no larger disc (represented by numbers here) comes before a smaller disc. I wanted to insert some kind of verification after every Move, so I tried to use the MaybeT monad transformer to send a failure down the list of moves:

verifiedMoves :: [MaybeT (State Towers) ()]
verifiedMoves = map ((>> verify) . return) moves
  where
    check :: [Int] -> Bool
    check [] = True
    check [_] = True
    check (x:y:ys) = (x < y) && check (y:ys)
    verify :: MaybeT (State Towers) ()
    verify = do
        (Towers xs ys zs) <- lift get
        guard (check xs && check ys && check zs)

Accordingly I changed the main monad:

main = maybe (putStrLn "violation") (const $ print finalTowers) v
  where
    (v, finalTowers) = runState (runMaybeT $ sequence_ verifiedMoves) initTowers

Now the output looks wrong, like no state changes occurred:

Towers [1,2,3,4,5] [] []

If I make the initial state invalid, it indeed fails the verification. So if there is no state change because the effects of the Moves got interrupted, I would expect the output to be the word "violation."

Why, after applying runMaybeT, is the result of applying runState equal to (Just (), Towers [1,2,3,4,5] [] [])?


Here is the rest of the code, for reference. I tried lifting the get and put monads in my pop and push functions, but that produced the same output.

import Control.Monad
import Data.Functor.Identity
import Control.Monad.State
import Control.Monad.Trans.Maybe
import qualified Data.Map as M

data Rod = First | Second | Third
    deriving (Show)

other :: Rod -> Rod -> Rod
other First Second = Third
other Second First = Third
other First Third = Second
other Third First = Second
other Second Third = First
other Third Second = First

getRod :: Towers -> Rod -> [Int]
getRod (Towers x y z) First  = x
getRod (Towers x y z) Second = y
getRod (Towers x y z) Third  = z

setRod :: Rod -> Towers -> [Int] -> Towers
setRod First t ds  = Towers ds r2 r3
  where
    r2 = t `getRod` Second
    r3 = t `getRod` Third
setRod Second t ds = Towers r1 ds r3
  where
    r1 = t `getRod` First
    r3 = t `getRod` Third
setRod Third t ds  = Towers r1 r2 ds 
  where
    r1 = t `getRod` First
    r2 = t `getRod` Second

pop :: Rod -> State Towers Int
pop r = do
    t <- get
    let ds = t `getRod` r
        d = head ds
        load = setRod r
    put $ t `load` (tail ds)
    return d

push :: Rod -> Int -> State Towers ()
push r d = do
    t <- get
    let ds = t `getRod` r
        load = setRod r
    put $ t `load` (d:ds)
like image 705
Alex Avatar asked Jul 06 '17 18:07

Alex


1 Answers

Look at this line

verifiedMoves = map ((>> verify) . return) moves

equivalent to

= map (\m -> return m >> verify) moves

but for all x, we have return x >> a = a, thus

= map (\_ -> verify) moves

So you discarded the moves. You probably meant to use lift instead of return there.

like image 190
Li-yao Xia Avatar answered Nov 15 '22 05:11

Li-yao Xia