10

我正在尝试用 Haskell 编写一个小游戏,并且需要传递大量的状态。我想尝试用 State 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) 的实例。我已经重写了一个非常相似的函数,除了它的 State 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 中编写空函数?
  2. 为什么我不能声明我的解决方法函数实际具有的类型?
4

1 回答 1

13

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

当你写:

getCurrentPlayerM = ...

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


为了解释我的意思,举这个更简单的例子:

pi = 3.14

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

如果我们有(2::Double) + pipi将是一个Double。如果我们有(3::Float) + pipi将是一个Float。每次pi使用时,都必须重新计算(因为我们不能存储所有可能的小数类型的替代版本,可以吗?)pi。这对于简单的文字来说很好,但是如果我们想要更多的小数并使用一种奇特的算法来计算它呢?我们不希望每次使用时都重新计算它,对吗?3.14pipi

这就是为什么 Haskell 报告指出顶级一元类型约束定义必须具有单一类型(单态)以避免这个问题。在这种情况下,pidefault获得Double. 如果需要,您可以使用default关键字更改默认数字类型:

default (Int, Float)

pi = 3.14 -- pi will now be Float

但是,在您的情况下,您将获得推断的签名:

getCurrentPlayerM :: MonadState GameState m => m P.Player

这意味着:“对于任何存储GameStates 的状态单子,检索一个玩家。” 然而,由于应用了单态限制,Haskell 被迫尝试通过为m. 但是,它找不到一个,因为没有像数字那样默认状态单子的类型,所以它放弃了。

你要么想给你的函数一个明确的类型签名:

getCurrentPlayerM :: MonadState GameState m => m P.Player

...但是您必须添加FlexibleContextsHaskell 语言扩展才能使其工作,方法是在文件顶部添加以下内容:

{-# LANGUAGE FlexibleContexts #-}

或者,您可以明确指定您想要的状态 monad:

getCurrentPlayerM :: State GameState P.Player

您还可以通过添加扩展来禁用单态限制;但是,添加类型签名要好得多。

{-# LANGUAGE NoMonomorphismRestriction #-}

PS。如果您有一个将您的状态作为参数的函数,您可以使用:

value <- gets getCurrentPlayer

您还应该考虑将LensesState monads一起使用,它可以让您为隐式状态传递编写非常简洁的代码。

于 2012-06-02T22:07:34.777 回答