我将通过向您展示做您想做的事情的惯用方式来回答您的问题。随着我的进展,我将指出我在您的代码中修复的内容。
第一个问题:您的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
and 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
。
使用push
and 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
,您可能需要阅读我写的这篇文章。
编辑:根据要求,这是无镜头版本:
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 )