我想尝试新的东西,所以我决定把头包裹在Haskell周围。来自主要的C#/ Java背景,我有一个关于更新各种值的问题。我已经设法将其缩小到一个问题。考虑以下功能:
appendHistory :: State -> Action -> State
appendHistory (State gs p1 p2 h) a = State gs p1 p2 (a : h)
这基本上将行动附加到州的历史上。在这种情况下,gs
,p1
和p2
相当无关紧要,因为我们正在尝试更新h
。为了复制它们需要命名的变量(所以我可以在=
的右侧使用它们)。我有什么方法可以写appendHistory
,以便h
得到更新,而无需明确指定gs
,p1
和p2
?
一个选项是让其他函数处理检索和更新状态。 appendHistory
现在不必指定其他参数。不是我的最爱,因为我们刚刚将问题转移到了updateHistory
。在哈斯克尔:
appendHistory :: State -> Action -> State
appendHistory s a = updateHistory s (a : (history s))
history :: State -> History
history (State _ _ _ h) = h
updateHistory :: State -> History -> State
updateHistory (State s p1 p2 _) h = State s p1 p2 h
另一种方法是使用记录。但我被告知(/读)使用相同名称时可能会发生名称冲突。我想我会有很多字段名为state
的记录,所以我暂时一直在避开这些记录。
我想,我想做的就是(非功能性):
void Update(State state, Action action) {
state.history.append(action);
}
有没有办法在Haskell中很好地做到这一点,而不必为每个data
参数写一个'getter'和'setter'?
完整参考:
type History = [Action]
type Deck = [Card]
type Hand = [Card]
type Graveyard = [Card]
data Card = Card -- Still to expand
data PlayerState = PlayerState Hand Deck Graveyard
data GameState = NotStarted | Playing | Finished
data State = State GameState PlayerState PlayerState History
Action
只是关于如何变异State
的定义。
答案 0 :(得分:2)
正如melpomene评论的那样,lenses是您问题的现代标准答案。您可以手动定义镜头
{-# LANGUAGE Rank2Types, TypeFamilies #-}
import Control.Lens
gameHistory :: Lens' State History
gameHistory = lens (\(State _ _ _ h) -> h)
(\(State gs p1 p2 _) h -> State gs p1 p2 h)
...或让模板Haskell为你做这件事
{-# LANGUAGE TemplateHaskell #-}
data State = State {
_gameState :: GameState
, _player₀State, _player₁State :: PlayerState
, _gameHistory :: History }
makeLenses ''State
自动创建
gameState :: Lens' State GameState
player₀State :: Lens' State GameState
player₁State :: Lens' State GameState
gameHistory :: Lens' State History
有了这样的镜头后,您可以使用operators来调整State
个对象:
appendHistory :: Action -> State -> State -- more conventional argument order in Haskell
appendHistory a = gameHistory %~ (a:)