如何将列表或 ListT 添加到此 monad 转换器?

How do I add lists or ListT to this monad transformer?

我有一个代表游戏当前状态的游戏记录。

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))

在给定的 GameState 上将函数写入 运行 和 Effect 非常简单。

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 实际上应该 return 一个 [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 添加到转换器,在 WriterRandomState 之外,因为我想要不同的分支计算具有不同的历史和独立的状态和随机生成器。但这不起作用。我收到以下类型错误。

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。每个分支都有一个独立的 StdGenHistory。我通过将所有内容放入 Game 记录并仅使用非单子函数来实现此效果。这有效,而且非常简单。然而,这些功能的组合真的很烦人,因为它们就像状态一样,我需要自己处理。这就是 monad 擅长的,所以我认为在这里重用它是明智的。遗憾的是,它的列表方面让我很困惑。

首先,错误的直接原因是runListT的类型是...

GHCi> :t runListT
runListT :: ListT m a -> m [a]

...但您使用它时就好像它产生了 [m a],而不是 m [a]。换句话说,result 定义中的 map 不应该存在。

其次,在单子堆栈中,内部单子统治着外部单子。例如,用 ListT 包装 StateT 会产生一个普通的有状态计算,而这种计算恰好会产生多个结果。我们可以看到,通过特化 runListT:

的类型
GHCi> :set -XTypeApplications
GHCi> :t runListT @(StateT _ _)
runListT @(StateT _ _) :: ListT (StateT t t1) a -> StateT t t1 [a]
另一方面,

StateT 包裹 ListT,为我们提供了一个产生多个状态和结果的计算:

GHCi> :t runStateT @_ @(ListT _)
runStateT @_ @(ListT _)
  :: StateT t (ListT t1) a -> t -> ListT t1 (a, t)

既然如此,您想交换堆栈中的变压器。如果你想对所有事物产生多重影响,正如你所描述的,并且你不需要 IO 作为你的基本单子,你根本不需要 ListT - 简单地说 [] 在堆栈的底部。

第三,切线一点,避免变形金刚中的ListT。众所周知,它是非法的,并且已被弃用 in the latest version of transformers. A simple replacement for it is provided by the list-t package. (If, at some point further down the road, you get to make use of the pipes streaming library, you might also find its own version of ListT 有用。)