使用状态monad隐藏显式状态

时间:2012-06-02 21:08:49

标签: haskell state monads monomorphism-restriction

我正在尝试在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是一个类型而不是类型类,但是为什么它在实践中被接受但是当我试图明确它时我不会更加困惑。

总结一下:

  1. 为什么我不能在State monad中编写nullary函数?
  2. 为什么我不能声明我的解决方法函数实际上具有的类型?

1 个答案:

答案 0 :(得分:14)

问题是你没有为你的函数写出类型签名,并且适用单态限制。

当你写:

getCurrentPlayerM = ...

您正在编写没有类型声明的顶级一元约束值定义,因此Haskell编译器将尝试推断定义的类型。然而,单态限制(字面意思:单形限制)表明所有具有推断类型约束的顶级定义必须解析为具体类型,即它们不能是多态的。


要解释我的意思,请采用这个更简单的例子:

pi = 3.14

在这里,我们定义pi没有类型,因此GHC推断类型Fractional a => a,即&#34;任何类型a,只要它可以被视为一个分数&#34;但是,这种类型存在问题,因为它意味着pi不是常量,即使它看起来像是。为什么?因为pi的值将根据我们想要的类型重新计算。

如果我们有(2::Double) + pipi将是Double。如果我们有(3::Float) + pipi将是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

您还应该考虑将LensesState monads一起使用,这样可以为隐式状态传递编写非常干净的代码。