所以我尝试使用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演讲者。提前致谢。
答案 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
请注意,您无需手动定义此类镜头。您也可以这样做而不是定义gamememory
和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
以返回Maybe
。这是更惯用的Haskell,而不是默认为0
或使用head
。
使用push
和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
本地化push
和pop
来操作gamememory
或gamestack
字段。 zoom
将镜头带到子场,然后运行有状态动作,好像整个状态只是那个子场。这很酷,因为现在push
和pop
更加可重用,我们不必将特定的状态数据类型选择加入其中。
这也使用.=
,它设置一个给定的字段。它基本上与:
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 } )