我写的是一个真正的monad?

nek*_*k28 5 monads haskell functional-programming

我一直在努力了解monad,因为我最近了解拉链是什么,我认为我可能会尝试将两种想法结合起来.(>> =)做我认为monad应该做的事情,即它让我以拉链的形式组合运动,moveRight >>= moveLeft >>= goAhead >>= return但我觉得我错过了一些东西,因为,除其他外,我似乎不适合它的类型一个单子应该是什么,即Ma -> (a -> Mb) -> Mb.欢迎任何帮助.

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
Run Code Online (Sandbox Code Playgroud)

K. *_*uhr 6

你的Movement类型很像Maybemonad 的组合(允许失败的动作)加上Statemonad和当前Zipper a的状态:

State (Zipper a) b  =  Zipper a -> (b, Zipper a)
Run Code Online (Sandbox Code Playgroud)

我在=这里作弊.这不是State类型的精确定义,但这些类型是同构的,因此您可以将其State视为等于此类型.

换句话说,你已经接近重新发明基于变压器的monad:

type Movement' a b = StateT (Zipper a) Maybe b
Run Code Online (Sandbox Code Playgroud)

主要区别在于Movement' a b同构为:

Zipper a -> Maybe (b, Zipper a)
Run Code Online (Sandbox Code Playgroud)

所以它有b你没有包含的额外价值.

SOOO ....

如果您要将Movement类型重写为:

type Movement a b = Zipper a -> Maybe (b, Zipper a)
Run Code Online (Sandbox Code Playgroud)

你会做点什么的.在这里,Movement不是monad - 相反,Movement a是一个可以应用于底层类型的monad Movement a b.

如果你熟悉Eithermonad,那就是同样的事情: Either它本身不是一个monad,而是Either String一个可以应用于另一种类型的monad,比如Either String Double代表一个返回Double结果或String错误的计算信息.

类似地,你Movement a是一个monad,可以应用于另一种类型Movement a b来表示一个计算,该计算返回一段b时间,保持一个Zipper a内部状态,并通过返回允许失败Nothing.

继续,你的turnLeft,turnRightgoAhead纯粹的效果:他们修改状态(Statemonad 的一部分),如果做出不可能的移动(monad 的一部分),则发出信号错误Maybe,但他们不需要返回任何东西.没关系,因为他们可以回来().这是怎么回事goAhead:

goAhead :: Movement a ()
-- same as:  goAhead :: Zipper a -> Maybe ((), Zipper a)
goAhead (t, Passage v a) = Just ((), (StraightAhead v:t, a))
goAhead _                = Nothing
Run Code Online (Sandbox Code Playgroud)

你可以对turnLeft和做出类似的改动turnRight.

现在,重新定义return相对容易.它应该将一个任意值的类型打包b到你的Movement amonad中而不会产生任何"效果".看看你是否可以填空:

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 -> _
Run Code Online (Sandbox Code Playgroud)

当然,(>>=)有点难.看看你是否能搞清楚:

(>>=) :: 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 ...
Run Code Online (Sandbox Code Playgroud)

如果你放弃,我已经在下面列出了答案.

有了这个monad,事情会变得更有趣.例如,您可以引入确实返回某些内容的操作.这组有效动作怎么样?

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)
Run Code Online (Sandbox Code Playgroud)

或当前位置的元素:

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)
Run Code Online (Sandbox Code Playgroud)

使用此方法,您可以构建一个遍历拉链的monadic动作,始终使用第一个有效的移动,并返回死角的值:

findDeadEnd :: Movement a a
findDeadEnd =
    validMoves >>= \moves ->
    case moves of [] -> peek
                  (mv:_) -> (case mv of StraightOk -> goAhead
                                        LeftOk     -> turnLeft
                                        RightOk    -> turnRight)
                            >>= \() -> findDeadEnd
Run Code Online (Sandbox Code Playgroud)

如果这是一个真正的monad实例,你可以用do符号更清晰地写上面的内容.

不错,是吗?

无论如何,完整的代码与答案return>>=以下.接下来,您可能想尝试将您的包装Movement成新类型,以便定义实例:

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
Run Code Online (Sandbox Code Playgroud)

并看看你是否可以重写所有内容以使其成为现实Monad.

完整的例子:

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
Run Code Online (Sandbox Code Playgroud)