0

因此,我正在尝试使用 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

上面的代码片段返回类型错误。我不太了解状态单子。那么如何使用 saveName 将游戏栈顶部的当前玩家姓名复制到游戏内存中,并在使用 getName 时将游戏内存推送到游戏栈顶部?

对不起,如果它有点混乱。我是一名 ESL 演讲者。提前致谢。

4

3 回答 3

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

请注意,您不必像这样手动定义镜头。除了定义gamememoryand gamestack,您也可以这样做:

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

import Control.Lens

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

makeLenses ''GameState

无论您选择哪种方式,一旦我们有了这些镜头,我们就可以编写push并且pop以这样一种方式,他们不关心他们正在采取什么状态,只要它是一个列表:

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为返回 a 。Maybe这比默认0使用或使用更惯用 Haskell head

使用pushand pop,在游戏内存和堆栈之间传输值变得非常容易:

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将镜头对准一个子场,然后运行有状态的动作,就好像整个状态就是那个子场一样。这很酷,因为现在push更加pop可重用,我们不必将特定的状态数据类型选择烘焙到它们中。

这也使用.=,它设置一个给定的字段。它与以下内容基本相同:

lens .= x = zoom lens (put x)

要了解有关镜头、(.=)和的更多信息zoom,您可能需要阅读我写的这篇文章

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

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     )
于 2013-11-13T02:01:05.607 回答
1

NewGameState是一个糟糕的名字——它根本不是一个新的游戏状态,它是一个带有状态的单子。我刚打电话Game

pushStackvspush - 你给了一个名为的签名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 } )
于 2013-11-13T03:04:51.730 回答
1

如果某物在 a 的右侧,<-则它必须在该 monad 中。所以你在这里想要的是

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

getName = popStack

因为saveName我们fmap gamememory超过了当前状态并将结果存储在而memory不是将其压入堆栈。我们实际上可以写这个,就get >>= pushStack . gamememory好像你想花哨一样。

popStack不接受任何争论,所以我不确定你想要什么。我最好的猜测是它应该只获取我们推送的姓氏,它只是对popStack.

于 2013-11-13T01:10:30.633 回答