和Haskell State一起玩

时间:2013-11-13 01:04:06

标签: haskell functional-programming state

所以我尝试使用State实现Haskell游戏,作为游戏的一部分,我想实现保存当前玩家名称的方法,并在调用时检索它。我有帮助函数popStack和pushStack,它们分别弹出并将值推入堆栈。

当前代码:

 import Control.Monad.State

 data Gamestate = Gamestate {
     gamestack :: [String],
     gamememory :: String
 }

 type NewGameState = State GameState

 popStack :: NewGameState String
 popStack = state $ \st -> case gamestack st of
     [] -> (0.0,st)
     x:xs -> (x,st { gamestack = xs })

 pushStack :: String -> NewGameState ()
 push d = modify  $ \st -> st { gamestack = d : gamestack st }

我已经为saveName和getName提出了以下代码。

saveName :: NewGameState ()
saveName = do
        memory <-head   
        pushStack $ x

getName :: NewGameState ()
getName = do
        memory <- head gamestack
        popStack $ memory

以上代码片段返回类型错误。我不太了解State Monads。那么如何使用saveName将游戏堆顶部的当前玩家名称复制到gamememory中,并在使用getName时将gamememory推送到gamestack的顶部?

抱歉,如果它有点混乱。我是ESL演讲者。提前致谢。

3 个答案:

答案 0 :(得分:2)

我将通过向您展示您正在尝试做的事情的惯用方式来回答您的问题。随着我的进展,我将指出我在你的代码中修复了什么。

第一个问题:Gamestate的大小写不一致。资本化在Haskell中很重要,因此我将所有内容重命名为GameState

因此,在进行修复之后,我做的第一件事就是为两个数据类型的字段定义镜头。这使得修改状态子集的有状态事物变得更容易。当我开始实现剩下的函数时,你会看到这个:

import Control.Monad.State
import Control.Lens

data GameState = GameState
    { _gamestack  :: [String]
    , _gamememory ::  String
    }

gamestack :: Lens' GameState [String]
gamestack k (GameState s m) = fmap (\s' -> GameState s' m) (k s)

gamememory :: Lens' GameState String
gamememory k (GameState s m) = fmap (\m' -> GameState s m') (k m)

type NewGameState = State GameState

请注意,您无需手动定义此类镜头。您也可以这样做而不是定义gamememorygamestack

{-# LANGUAGE TemplateHaskell #-}  -- Note the extension

import Control.Lens

data GameState = GameState
    { _gamestack  :: [String]
    , _gamememory ::  String
    }

makeLenses ''GameState

无论您选择哪种方式,一旦我们拥有这些镜头,我们就可以写出pushpop这样他们不关心他们所处的状态,只要它是清单:

pop :: State [a] (Maybe a)
pop = do
    s <- get
    case s of
        []   -> return Nothing
        x:xs -> do
            put xs
            return (Just x)

push :: a -> State [a] ()
push d = modify (d:)

请注意,如果列表为空,我更改了pop以返回Maybe。这是更惯用的Haskell,而不是默认为0或使用head

使用pushpop,可以非常轻松地在游戏内存和堆叠之间传输值:

saveName :: NewGameState ()
saveName = do
    memory <- use gamememory
    zoom gamestack (push memory)

getName :: NewGameState ()
getName = do
    m <- zoom gamestack pop
    case m of
        Nothing -> return ()
        Just x  -> gamememory .= x

请注意我如何使用zoom本地化pushpop来操作gamememorygamestack字段。 zoom将镜头带到子场,然后运行有状态动作,好像整个状态只是那个子场。这很酷,因为现在pushpop更加可重用,我们不必将特定的状态数据类型选择加入其中。

这也使用.=,它设置一个给定的字段。它基本上与:

相同
lens .= x = zoom lens (put x)

要详细了解镜头,(.=)zoom,您可能需要阅读我写的this post

编辑:根据要求,这是无镜头版本:

import Control.Monad.State

data GameState = GameState
    { gamestack  :: [String]
    , gamememory ::  String
    }

type NewGameState = State GameState

saveName :: NewGameState ()
saveName = do
    GameState stack memory <- get
    put (GameState (memory:stack) memory)

getName :: NewGameState ()
getName = do
    GameState stack memory <- get
    case stack of
        []   -> put (GameState stack memory)
        x:xs -> put (GameState xs    x     )

答案 1 :(得分:1)

如果某个内容位于<-的右侧,则必须位于该单子中。所以你想要的是

saveName :: NewGameState ()
saveName = do
  memory <- fmap gamememory get
  pushStack memory

getName = popStack

对于saveName我们fmap gamememory覆盖当前状态并将结果存储在memory中,而不是将其压缩到堆栈上。如果你想要的话,我们实际上可以把它写成get >>= pushStack . gamememory

popStack不接受任何争论,所以我不确定你想要什么。我最好的猜测是它应该抓住我们推送的姓氏,这就是对popStack的调用。

答案 2 :(得分:1)

NewGameState 是一个糟糕的名字 - 它根本不是一个新的游戏状态,它是一个带有状态的monad。我刚才叫它Game

pushStack vs push - 您提供了一个名为pushStack的签名,然后是一个名为push的函数。选一个。

popStack [] -> (0.0, st) 让我们面对它,0.0不是字符串,那你为什么要回来呢?弹出一个空堆栈时你真的不知道该怎么办吗?您如何使用""呢?

saveName和getName 嗯,你甚至没有说过你想要做什么。看来你接受了其他回答者的解释,所以我们可以使用记录更新语法。

最后,这里有一些至少编译的代码:

import Control.Monad.State

data GameState = GameState {
    gamestack :: [String],
    gamememory :: String
}

type Game = State GameState

popStack :: Game (Maybe String)
popStack = state $ \st -> case gamestack st of
    [] -> (Nothing,st)
    x:xs -> (Just x,st { gamestack = xs })

pushStack :: String -> Game ()
pushStack d = modify  $ \st -> st { gamestack = d : gamestack st }

saveName :: Game ()
saveName = do
    memory <- gamememory `fmap` get
    pushStack memory

getName :: Game ()
getName = do
    newMem <- popStack
    case newMem of
       Nothing -> return ()
       Just n  -> modify (\x -> x { gamememory = n } )