I have been trying to understand monads and since I recently understood what zippers are I thought I might try to combine both ideas. (>>=) does what I think monads should do, namely it lets me combine movements around the zipper in the form of moveRight >>= moveLeft >>= goAhead >>= return but I feel like I'm missing something because, among other things, I can't seem to fit its type to what a monad should be, namely Ma -> (a -> Mb) -> Mb. Any help would be welcome.
module MonadZipper where
import Prelude hiding (return, (>>=))
data Node a = Fork a (Node a) (Node a)
| Passage a (Node a)
| DeadEnd a
deriving (Show)
data Branch a = TurnLeft a (Node a)
| TurnRight a (Node a)
| StraightAhead a
deriving (Show)
type Trace a = [Branch a]
type Zipper a = (Trace a, Node a)
type Movement a = Zipper a -> Maybe (Zipper a)
--not sure whether this wrapping makes sense
turnLeft :: Zipper a -> Maybe (Zipper a)
turnLeft (t, (Fork v l r)) = Just (TurnLeft v r:t, l)
turnLeft _ = Nothing
turnRight :: Zipper a -> Maybe (Zipper a)
turnRight (t, (Fork v l r)) = Just (TurnRight v l:t, r)
turnRight _ = Nothing
goAhead :: Zipper a -> Maybe (Zipper a)
goAhead (t, Passage v a) = Just (StraightAhead v:t, a)
goAhead _ = Nothing
(>>=) :: Movement a -> Movement a -> Movement a
(>>=) turner func = \zippo ->
case turner zippo of
Nothing -> Nothing
Just tree -> func tree
return :: Zipper a -> Maybe (Zipper a)
return tree = Just tree
From Wikipedia: A monad is a certain type of endofunctor. For example, if F and G are a pair of adjoint functors, with F left adjoint to G, then the composition G o F is a monad. If F and G are inverse functors, the corresponding monad is the identity functor.
What is a Monad? A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.
monads are used to address the more general problem of computations (involving state, input/output, backtracking, ...) returning values: they do not solve any input/output-problems directly but rather provide an elegant and flexible abstraction of many solutions to related problems.
Monads are not about ordering/sequencing Just as you can use monads for state, or strictness, you can use them to order computations. But there are also commutative monads, like Reader, that don't order anything. So ordering is not in any way essential to what a monad is.
Your Movement type is a lot like a combination of the Maybe monad (to allow for failed movements) plus the State monad with the current Zipper a as the state:
State (Zipper a) b = Zipper a -> (b, Zipper a)
I'm cheating with the = here . This isn't the precise definition of the State type, but these types are isomorphic, so you can think of State as being equal to this type.
In other words, you've come close to reinventing the transformer-based monad:
type Movement' a b = StateT (Zipper a) Maybe b
The main difference is that Movement' a b is isomorphic to:
Zipper a -> Maybe (b, Zipper a)
so it's got that extra b value that you haven't included.
Sooo....
If you were to rewrite your Movement type as:
type Movement a b = Zipper a -> Maybe (b, Zipper a)
you'd be on to something. Here, Movement isn't a monad -- instead, Movement a is a monad which can be applied to an underlying type Movement a b.
If you're familiar with Either as a monad, it's the same thing: Either by itself isn't a monad, but Either String is a monad that can be applied to another type like Either String Double to represent, say, a computation that returns either a Double result or a String error message.
Similarly, your Movement a is a monad that can be applied to another type Movement a b to represent a computation that returns a b while maintaining a Zipper a as an internal state and allowing failure by returning Nothing.
Moving on, your turnLeft, turnRight, and goAhead are pure effects: they modify the state (the State part of the monad), signalling error if an impossible move is made (the Maybe part of the monad), but they don't need to return anything. That's okay, because they can return (). Here's how goAhead would work:
goAhead :: Movement a ()
-- same as: goAhead :: Zipper a -> Maybe ((), Zipper a)
goAhead (t, Passage v a) = Just ((), (StraightAhead v:t, a))
goAhead _ = Nothing
and you can make analogous changes to turnLeft and turnRight.
Now, redefining return is relatively easy. It should pack an arbitrary value of type b into your Movement a monad without having any "effects". See if you can fill in the blank:
return :: b -> Movement a b
-- same as: return :: b -> Zipper a -> Maybe (b, Zipper a)
-- in definitino below, right hand side should be:
-- Movement a b = Zipper a -> Maybe (b, Zipper a)
return b = \z -> _
Of course, (>>=) is a little harder. See if you can figure it out:
(>>=) :: Movement a b -> (b -> Movement a c) -> Movement a c
-- in definition below, right-hand side is a:
-- Movement a c = Zipper a -> Maybe (b, Zipper a)
mb >>= bToMc = \z1 -> case mb z1 of ...
If you give up, I've included the answers below.
With this monad, things can get a little more interesting. For example, you can can introduce an action that does return something. How about the set of valid moves?
data Move = LeftOk | RightOk | StraightOk
validMoves :: Movement a [Move]
validMoves z@(t, n) = case n of
(Fork _ _ _) -> Just ([LeftOk, RightOk], z)
(Passage _ _) -> Just ([StraightOk], z)
(DeadEnd _) -> Just ([], z)
or the element at the current position:
peek :: Movement a a
peek z@(_, n) = case n of
Fork a _ _ -> Just (a, z)
Passage a _ -> Just (a, z)
DeadEnd a -> Just (a, z)
Using this, you can construct a monadic action that walks the zipper, always using the first valid move, and returns the value at the dead end:
findDeadEnd :: Movement a a
findDeadEnd =
validMoves >>= \moves ->
case moves of [] -> peek
(mv:_) -> (case mv of StraightOk -> goAhead
LeftOk -> turnLeft
RightOk -> turnRight)
>>= \() -> findDeadEnd
If this was a real monad instance, you could write the above more cleanly in do notation.
Not bad, huh?
Anyway, the full code with the answers for return and >>= is below. Next, you might want to try wrapping your Movement into a newtype so you can define instances:
newtype Movement a b
= Movement { runMovement :: Zipper a -> Maybe (b, Zipper a) }
instance Functor (Movement a) where
instance Applicative (Movement a) where
instance Monad (Movement a) where
and see if you can rewrite everything to make it a real Monad.
The full example:
module MonadZipper where
import Prelude hiding (return, (>>=))
data Node a = Fork a (Node a) (Node a)
| Passage a (Node a)
| DeadEnd a
deriving (Show)
data Branch a = TurnLeft a (Node a)
| TurnRight a (Node a)
| StraightAhead a
deriving (Show)
type Trace a = [Branch a]
type Zipper a = (Trace a, Node a)
type Movement a b = Zipper a -> Maybe (b, Zipper a)
(>>=) :: Movement a b -> (b -> Movement a c) -> Movement a c
mb >>= bToMc = \z1 -> case mb z1 of
Nothing -> Nothing
Just (b, z2) -> bToMc b z2
return :: b -> Movement a b
return b z = Just (b, z)
turnLeft :: Movement a ()
turnLeft (t, (Fork v l r)) = Just ((), (TurnLeft v r:t, l))
turnLeft _ = Nothing
turnRight :: Movement a ()
turnRight (t, (Fork v l r)) = Just ((), (TurnRight v l:t, r))
turnRight _ = Nothing
goAhead :: Movement a ()
goAhead (t, Passage v a) = Just ((), (StraightAhead v:t, a))
goAhead _ = Nothing
data Move = LeftOk | RightOk | StraightOk
validMoves :: Movement a [Move]
validMoves z@(t, n) = case n of
(Fork _ _ _) -> Just ([LeftOk, RightOk], z)
(Passage _ _) -> Just ([StraightOk], z)
(DeadEnd _) -> Just ([], z)
peek :: Movement a a
peek z@(_, n) = case n of
Fork a _ _ -> Just (a, z)
Passage a _ -> Just (a, z)
DeadEnd a -> Just (a, z)
findDeadEnd :: Movement a a
findDeadEnd =
validMoves >>= \moves ->
case moves of [] -> peek
(mv:_) -> (case mv of StraightOk -> goAhead
LeftOk -> turnLeft
RightOk -> turnRight)
>>= \() -> findDeadEnd
test = case findDeadEnd ([], (Fork 1 (Fork 2 (Passage 3 (DeadEnd 4))
(DeadEnd 5))
(Passage 6 (DeadEnd 7)))) of
Just (v, _) -> print v
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With