Har*_*sen 10 monads state haskell monomorphism-restriction
我正试图在Haskell写一个小游戏,并且有相当多的状态需要传递.我想尝试用状态monad隐藏状态
现在我遇到了一个问题:带状态和参数的函数很容易写入状态monad中.但也有一些函数只是将状态作为参数(并返回修改后的状态,或者可能是其他东西).
在我的代码的一部分中,我有这一行:
let player = getCurrentPlayer state
Run Code Online (Sandbox Code Playgroud)
我希望它不采取状态,而是写
player <- getCurrentPlayerM
Run Code Online (Sandbox Code Playgroud)
目前,它的实现看起来像这样
getCurrentPlayer gameState =
(players gameState) ! (on_turn gameState)
Run Code Online (Sandbox Code Playgroud)
通过这样编写它似乎很简单,使它在State monad中工作:
getCurrentPlayerM = do state <- get
return (players state ! on_turn state)
Run Code Online (Sandbox Code Playgroud)
然而,这引起了ghc的抱怨!它说,没有使用"get"产生的(MonadState GameState m0)实例.我已经重写了一个非常相似的函数,除了它的状态monad形式不是nullary,所以在预感中,我重写了这样:
getCurrentPlayerM _ = do state <- get
return (players state ! on_turn state)
Run Code Online (Sandbox Code Playgroud)
果然,它的确有效!但当然我必须把它称为getCurrentPlayerM(),我觉得有点傻.传递参数是我想要首先避免的!
另外一个惊喜:看看我得到的ghci类型
getCurrentPlayerM :: MonadState GameState m => t -> m P.Player
Run Code Online (Sandbox Code Playgroud)
但是如果我尝试在我的代码中明确地设置它,我会得到另一个错误:"约束MonadState GameState m中的非类型变量参数"以及允许它的语言扩展的提议.我想这是因为我的GameState是一种类型,而不是类型类,但是为什么它在实践中被接受,但是当我试图明确它时,我更加困惑.
总结一下:
dfl*_*str 14
问题是您没有为函数编写类型签名,并且适用单态限制.
当你写:
getCurrentPlayerM = ...
Run Code Online (Sandbox Code Playgroud)
您正在编写没有类型声明的顶级一元约束值定义,因此Haskell编译器将尝试推断定义的类型.但是,单态限制(字面意思:单形限制)表明所有具有推断类型约束的顶级定义必须解析为具体类型,即它们不能是多态的.
为了解释我的意思,请举一个更简单的例子:
pi = 3.14
Run Code Online (Sandbox Code Playgroud)
在这里,我们定义pi没有类型,所以GHC推断出类型Fractional a => a,即"任何类型a,只要它可以被视为一个分数".但是,这种类型是有问题的,因为它意味着它pi不是常数,即使它看起来像.为什么?因为pi将根据我们想要的类型重新计算值.
如果我们有(2::Double) + pi,pi将是一个Double.如果我们有(3::Float) + pi,pi将是一个Float.每一次pi使用,因此它必须重新计算(因为我们不能存储的替代版本pi为所有可能的分数类型的,对吗?).这对于简单的文字很好3.14,但如果我们想要更多的小数pi并使用计算它的奇特算法怎么办?我们不希望每次pi使用时重新计算它,是吗?
这就是为什么Haskell报告声明顶级一元类型约束定义必须具有单一类型(单态),以避免这个问题.在这种情况下,pi将获得default类型Double.如果需要,可以使用default关键字更改默认数字类型:
default (Int, Float)
pi = 3.14 -- pi will now be Float
Run Code Online (Sandbox Code Playgroud)
但是,在您的情况下,您将获得推断签名:
getCurrentPlayerM :: MonadState GameState m => m P.Player
Run Code Online (Sandbox Code Playgroud)
这意味着:"对于存储GameStates的任何州monad ,检索玩家." 但是,由于单态限制适用,Haskell被迫尝试通过选择具体类型来使这种类型成为非多态的m.然而,它找不到一个,因为没有类型默认状态monad像数字一样,所以它放弃了.
您要么为您的函数提供显式类型签名:
getCurrentPlayerM :: MonadState GameState m => m P.Player
Run Code Online (Sandbox Code Playgroud)
...但是你需要添加FlexibleContextsHaskell语言扩展才能工作,方法是在文件的顶部添加:
{-# LANGUAGE FlexibleContexts #-}
Run Code Online (Sandbox Code Playgroud)
或者,您可以明确指定所需的状态monad:
getCurrentPlayerM :: State GameState P.Player
Run Code Online (Sandbox Code Playgroud)
你也可以通过添加扩展名来禁用单态限制; 但是,添加类型签名要好得多.
{-# LANGUAGE NoMonomorphismRestriction #-}
Run Code Online (Sandbox Code Playgroud)
PS.如果您有一个将状态作为参数的函数,则可以使用:
value <- gets getCurrentPlayer
Run Code Online (Sandbox Code Playgroud)
您还应该考虑使用带状态monad的镜头,这样可以为隐式状态传递编写非常干净的代码.