在Haskell中组合monad

Ral*_*lph 10 io haskell state-monad

我正在尝试将Spider Solitaire播放器编写为Haskell学习练习.

我的main函数将为playGame每个游戏(使用mapM)调用一次函数,传入游戏编号和随机生成器(StdGen).该playGame函数应该返回一个Control.Monad.Statemonad和一个IO monad,其中包含一个String显示游戏画面并Bool指示游戏是赢还是输的.

如何将Statemonad与IOmonad 结合起来获得返回值?`playGame的类型声明应该是什么?

playGame :: Int -> StdGen a -> State IO (String, Bool)
Run Code Online (Sandbox Code Playgroud)

是对的State IO (String, Bool)吗?如果不是,它应该是什么?

main,我计划使用

do
  -- get the number of games from the command line (already written)
  results <- mapM (\game -> playGame game getStdGen) [1..numberOfGames]
Run Code Online (Sandbox Code Playgroud)

这是正确的打电话playGame吗?

Gab*_*lez 13

你想要的是StateT s IO (String, Bool),StateT两者Control.Monad.State(来自mtl包装)和Control.Monad.Trans.State(来自transformers包装)提供的地方.

这种一般现象称为monad变换器,您可以在Monad变换器中逐步阅读它们的精彩介绍.

定义它们有两种方法.其中一个在transformers包中找到,它使用MonadTrans类来实现它们.第二种方法在mtl类中找到,并为每个monad使用单独的类型.

transformers方法的优点是使用单个类类来实现所有内容(在此处找到):

class MonadTrans t where
    lift :: Monad m => m a -> t m a
Run Code Online (Sandbox Code Playgroud)

lift有两个很好的属性,任何MonadTrans必须满足的实例:

(lift .) return = return
(lift .) f >=> (lift .) g = (lift .) (f >=> g)
Run Code Online (Sandbox Code Playgroud)

这些都是变相,其中仿函数法(lift .) = fmap,return = id(>=>) = (.).

mtl类型类的方法有它的好处,也有些事只能使用干净地解决mtl型类,不过它的缺点是那么每个mtl类型的类都有自己的一套法律,你必须记住它实现实例时.例如,MonadError类型类(在此处找到)定义为:

class Monad m => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a
Run Code Online (Sandbox Code Playgroud)

这个课程也有法律规定:

m `catchError` throwError = m
(throwError e) `catchError` f = f e
(m `catchError` f) `catchError` g = m `catchError` (\e -> f e `catchError` g)
Run Code Online (Sandbox Code Playgroud)

这些只是伪装的monad法律,其中throwError = returncatchError = (>>=)(和monad法律是伪装的类别法,在哪里return = id(>=>) = (.)).

对于您的具体问题,您编写程序的方式是相同的:

do
  -- get the number of games from the command line (already written)
  results <- mapM (\game -> playGame game getStdGen) [1..numberOfGames]
Run Code Online (Sandbox Code Playgroud)

...但是当你编写你的playGame函数时,它看起来像:

-- transformers approach :: (Num s) => StateT s IO ()
do x <- get
   y <- lift $ someIOAction
   put $ x + y

-- mtl approach :: (Num s, MonadState s m, MonadIO m) => m ()
do x <- get
   y <- liftIO $ someIOAction
   put $ x + y
Run Code Online (Sandbox Code Playgroud)

当你开始堆叠多个monad变换器时,这些方法之间的差异会变得更加明显,但我认为这是一个好的开始.


Ric*_* T. 8

State是一个单子,IO是一个单子.您尝试从头开始编写的内容称为"monad转换器",Haskell标准库已经定义了您需要的内容.

看一下状态monad变换器StateT:它有一个参数,它是你要包装的内部monad State.

每个monad变换器实现了一堆类型类,这样每个实例,变换器每次都可以处理它(例如状态变换器只能直接处理与状态相关的函数),或者它将调用传播到内部monad以这种方式,当你可以堆叠你想要的所有变换器,并有一个统一的界面来访问所有变换器的功能.这是一个老的责任链,如果你想看看这种方式.

如果您查看hackage,或快速搜索堆栈溢出或谷歌,你会发现许多使用的例子StateT.

编辑:另一个有趣的读物是Monad变形金刚解释.