2012-06-02 80 views
9

我想在Haskell写一个小游戏,并且有相当数量的状态需要传递。我想尝试隐藏状态monad使用状态monad隐藏显式状态

现在我遇到了一个问题:功能,采取国家和一个参数很容易写入工作在状态单子。但也有一些函数只是将状态作为参数(并返回一个修改的状态,或者可能是其他的)。

在我的代码的一部分,我有这样一行:

let player = getCurrentPlayer state 

我想它不会采取状态,而不是写

player <- getCurrentPlayerM 

目前,它的实现看起来像这样

getCurrentPlayer gameState = 
    (players gameState) ! (on_turn gameState) 

它似乎很简单,使它在国家单体中的工作,通过这样写:

getCurrentPlayerM = do state <- get 
         return (players state ! on_turn state) 

然而,这引起ghc的投诉!它说,没有使用“get”引起的(MonadState GameState m0)实例。我已经重写了非常相似的功能,但并不是在其国家单子形式无参,等等预感,我重写这样的:

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. 为什么我不能写在单子国家职能零元?
  2. 为什么我不能声明我的解决方法函数实际上具有的类型?

回答

14

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

当你写:

getCurrentPlayerM = ... 

你正在写一个顶层一元约束值定义没有类型声明,所以Haskell编译将尝试推断定义一个类型。然而,单态限制(Literally:single-shape restriction)指出所有具有推断类型约束的顶级定义必须解析为具体类型,即它们不能是多态。


要解释一下我的意思是,采取这种简单的例子:

pi = 3.14 

在这里,我们定义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 

在你的情况,但是,你所得到的推断签名:

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

它的意思是:“对于任何国家monad,存储GameState s,找回玩家。“然而,由于单态限制的应用,Haskell被迫尝试通过为m选择一个具体类型来使这种类型变为非多态。然而,它找不到一个,因为没有像状态数字那样的状态monad的默认类型,所以它放弃了。

要么你想给你的功能一个明确的类型签名:

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

...但你必须添加FlexibleContexts的Haskell语言扩展为它工作,通过在顶部加入这个你文件:

{-# LANGUAGE FlexibleContexts #-} 

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

getCurrentPlayerM :: State GameState P.Player 

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

{-# LANGUAGE NoMonomorphismRestriction #-} 

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

value <- gets getCurrentPlayer 

你也应该考虑使用LensesState monads,它可以让你写的隐性状态的传球非常干净的代码。

+1

明确说明我想要哪个monad似乎是正确的解决方案。是的,我已经遇到了记录中的记录问题,并且读了一些关于Lenses的内容(其中包括我在这里搜索答案的时候,它似乎被推荐了很多!)。谢谢,优秀的解释! –