我正在尝试在Haskell中编写一个小游戏,并且有相当多的状态需要传递。我想尝试用State monad隐藏状态
现在我遇到了一个问题:使用状态和参数的函数很容易写入状态monad中。但也有一些函数只是将状态作为参数(并返回修改后的状态,或者可能是其他东西)。
在我的代码的一部分中,我有这一行:
let player = getCurrentPlayer state
我希望它不采取状态,而是写
player <- getCurrentPlayerM
目前,它的实现看起来像这样
getCurrentPlayer gameState =
(players gameState) ! (on_turn gameState)
通过这样编写它似乎很简单,使它在State monad中工作:
getCurrentPlayerM = do state <- get
return (players state ! on_turn state)
然而,这引起了ghc的抱怨!它说,没有使用“get”产生的(MonadState GameState m0)实例。我已经重写了一个非常相似的函数,除了它的状态monad形式不是非常的,所以在预感中,我重写了这样:
getCurrentPlayerM _ = do state <- get
return (players state ! on_turn state)
果然,它有效!但当然我必须把它称为getCurrentPlayerM(),我觉得有点傻。传递参数是我想要首先避免的事情!
另外一个惊喜:看看ghci中的类型我得到了
getCurrentPlayerM :: MonadState GameState m => t -> m P.Player
但是如果我尝试在我的代码中明确地设置它,我会得到另一个错误:“约束MonadState GameState m中的非类型变量参数”以及允许它的语言扩展的提议。我想这是因为我的GameState是一个类型而不是类型类,但是为什么它在实践中被接受但是当我试图明确它时我不会更加困惑。
总结一下:
答案 0 :(得分:14)
问题是你没有为你的函数写出类型签名,并且适用单态限制。
当你写:
getCurrentPlayerM = ...
您正在编写没有类型声明的顶级一元约束值定义,因此Haskell编译器将尝试推断定义的类型。然而,单态限制(字面意思:单形限制)表明所有具有推断类型约束的顶级定义必须解析为具体类型,即它们不能是多态的。
要解释我的意思,请采用这个更简单的例子:
pi = 3.14
在这里,我们定义pi
没有类型,因此GHC推断类型Fractional a => a
,即&#34;任何类型a
,只要它可以被视为一个分数&#34;但是,这种类型存在问题,因为它意味着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
但是,在您的情况下,您将获得推断签名:
getCurrentPlayerM :: MonadState GameState m => m P.Player
这意味着:&#34;对于存储GameState
的任何状态monad,检索播放器。&#34;但是,由于单态性限制适用,Haskell被迫尝试通过为m
选择具体类型来使此类型为非多态。然而,它找不到一个,因为状态monad没有类型默认类似于数字,所以它放弃了。
您要么为您的函数提供显式类型签名:
getCurrentPlayerM :: MonadState GameState m => m P.Player
...但是您必须添加FlexibleContexts
Haskell语言扩展才能使用它,方法是在文件顶部添加:
{-# LANGUAGE FlexibleContexts #-}
或者,您可以明确指定所需的状态monad:
getCurrentPlayerM :: State GameState P.Player
您还可以通过添加扩展名来禁用单态限制;但是,添加类型签名要好得多。
{-# LANGUAGE NoMonomorphismRestriction #-}
PS。如果你有一个以州为参数的功能,你可以使用:
value <- gets getCurrentPlayer
您还应该考虑将Lenses与State monads一起使用,这样可以为隐式状态传递编写非常干净的代码。