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

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


我想做的是在ListTWriterRandom之外将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。每个分支都有一个独立的StdGenHistory。我通过将所有内容放入Game记录中并仅使用非monadic函数来实现效果来完成此操作。这行得通,而且非常简单。但是,这些功能的组合确实很烦人,因为它们的行为像状态一样,我需要自己处理。这是monad擅长的事情,所以我想再用一遍是明智的。可悲的是,它的清单方面确实让我感到困惑。

最佳答案

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

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


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

其次,在单子堆栈中,内部的单子统治外部的单子。例如,用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作为基本单子,则完全不需要ListT-只需将[]放在堆栈。

第三,切记,避免使用ListT变压器。已知它是非法的,并且已弃用in the latest version of transformerslist-t package提供了一个简单的替代方法。 (如果在以后的某个时候,您开始使用管道流库,您可能会发现its own version of ListT很有用。)

08-26 15:08