我有一个代表游戏当前状态的游戏记录。
data Game = Game { score :: Int, turn :: Int }
我希望能够创建一系列功能来改变游戏状态,并且还可以使用随机数生成器以及记录从一个状态到另一个状态的事件。所以我创建了一个包含附加信息的GameState
记录。
type History = [String]
data GameState = GameState Game StdGen History
现在我想为将要对此GameState
起作用的函数创建数据类型。他们将被强制建模为游戏的更新以及滚动骰子和记录发生的事情。所以我创建了一个我想要的所有效果的monad变换器。
type Effect = WriterT History (RandT StdGen (State Game))
编写函数以在给定的Effect
上运行GameState
非常简单。
runEffect :: GameState -> Effect () -> GameState
runEffect (GameState game stdGen history) effect =
let ((((), newHist), newGen), newGame) =
runState (runRandT (runWriterT effect) stdGen) game
in GameState newGame newGen newHist
完美。现在我想建模另外一件事。某些Effects
可能会有多个不同的结果GameStates
。所以我的runEffect
实际应该返回[GameState]
。我可能需要将ListT
添加到这个monad变换器中。然后,如果需要,我的所有Effects
都可以选择生成多个结果。但是,如果它们只是一对一的映射,那么也可以这样做。
我尝试进行以下更改:
type Effect2 = ListT (WriterT [String] (RandT StdGen (State Game)))
runEffect2 :: GameState -> Effect2 a -> [GameState]
runEffect2 (GameState game stdGen history) effect =
let l = runListT effect
result = map (\e->runState (runRandT (runWriterT e) stdGen) game) l
in map (\((((), newHist), newGen), newGame)->
GameState newGame newGen newHist)
result
我尝试做的是将ListT
添加到Writer
以及Random
和State
之外的转换器,因为我想要不同的分支机构计算具有不同的历史和独立状态以及随机生成器。但这并不奏效。我收到以下类型错误。
Prelude λ: :reload [1 of 1] Compiling Main ( redux.hs, interpreted )
redux.hs:31:73: error:
• Couldn't match expected type ‘[WriterT
w
(RandT StdGen (StateT Game Data.Functor.Identity.Identity))
a1]’
with actual type ‘WriterT [String] (RandT StdGen (State Game)) [a]’
• In the second argument of ‘map’, namely ‘l’
In the expression:
map (\ e -> runState (runRandT (runWriterT e) stdGen) game) l
In an equation for ‘result’:
result
= map (\ e -> runState (runRandT (runWriterT e) stdGen) game) l
• Relevant bindings include
result :: [(((a1, w), StdGen), Game)] (bound at redux.hs:31:7)
l :: WriterT [String] (RandT StdGen (State Game)) [a]
(bound at redux.hs:30:7)
effect :: Effect2 a (bound at redux.hs:29:44)
runEffect2 :: GameState -> Effect2 a -> [GameState]
(bound at redux.hs:29:1)
Failed, modules loaded: none.
有谁知道我做错了什么?我实际上希望能够将一个GameState
扩展为多个GameStates
。每个分支都有一个独立的StdGen
和History
。我通过将所有内容放入Game
记录并仅使用非monadic函数来完成此操作。这很有效,而且很直接。然而,这些功能的组合真的很烦人,因为它们像状态一样,我需要自己处理它。这是monad很擅长的,所以我认为重用这里是明智的。可悲的是,它的列表方面让我非常困惑。
答案 0 :(得分:2)
首先,错误的直接原因是runListT
的类型是......
GHCi> :t runListT
runListT :: ListT m a -> m [a]
...但您正在使用它,好像它产生了[m a]
,而不是m [a]
。换句话说,map
定义中的result
不应该存在。
其次,在monadic堆栈中,内部monad统治外部monad。例如,StateT
与ListT
进行换行会导致花园种类的有状态计算恰好产生多个结果。我们可以通过专门化runListT
:
GHCi> :set -XTypeApplications
GHCi> :t runListT @(StateT _ _)
runListT @(StateT _ _) :: ListT (StateT t t1) a -> StateT t t1 [a]
另一方面,用ListT
包裹StateT
会给我们一个产生多个状态和结果的计算:
GHCi> :t runStateT @_ @(ListT _)
runStateT @_ @(ListT _)
:: StateT t (ListT t1) a -> t -> ListT t1 (a, t)
就是这样,你想要交换堆栈中的变换器。如果您想要为所有内容创建多个效果,正如您所描述的那样,并且您不需要IO
作为基本monad,则根本不需要ListT
- 只需添加[]
在堆栈的底部。
第三,在切线上,避免使用变换器中的ListT
。它被称为非法,并且已被弃用in the latest version of transformers。 list-t package提供了一个简单的替代品。 (如果在接下来的某个时候,您可以使用管道流式库,您可能还会发现its own version of ListT
有用。)