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 = return和catchError = (>>=)(和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变换器时,这些方法之间的差异会变得更加明显,但我认为这是一个好的开始.
State是一个单子,IO是一个单子.您尝试从头开始编写的内容称为"monad转换器",Haskell标准库已经定义了您需要的内容.
看一下状态monad变换器StateT:它有一个参数,它是你要包装的内部monad State.
每个monad变换器实现了一堆类型类,这样每个实例,变换器每次都可以处理它(例如状态变换器只能直接处理与状态相关的函数),或者它将调用传播到内部monad以这种方式,当你可以堆叠你想要的所有变换器,并有一个统一的界面来访问所有变换器的功能.这是一个老的责任链,如果你想看看这种方式.
如果您查看hackage,或快速搜索堆栈溢出或谷歌,你会发现许多使用的例子StateT.
编辑:另一个有趣的读物是Monad变形金刚解释.