如何从 main 启动 monad 转换器堆栈?

How to kickstart monad transformer stack from main?

这是我上一个问题的后续问题:

我的目标是为文件创建一个简单的文本编辑器。我已经有了一个 Editor 组件,它很好地封装了对底层数据结构的所有编辑操作。

感谢我之前问题的答案,我能够重构我的程序,这样我现在就有了一个不错的 monad 转换器堆栈:

type Session = StateT AppState (StateT Editor IO)

AppState 保存应用程序的全局状态(当前打开的文件等),而 Editor 表示应用程序编辑组件的内部状态(其中插入符是在,等等...)。我有一个函数是应用程序的主要驱动程序:

eventLoop :: Session ()

到目前为止一切顺利,但是现在我不知道如何从我的 main 函数中实际启动我的转换器堆栈? Main 必须 return IO monad 中的某些东西,它位于我的堆栈的最底部。我的猜测是我必须初始化我的 AppState 然后执行类似的操作:

main = do
  let initialAppState = ...
  return $ runStateT eventLoop initialAppState

但是我现在在哪里初始化我的 Editor

最让我困惑的是,在重构之前,Editor 只是 AppState:

的成员
data AppState = { editor :: Editor , ... }

但现在它已经从 AppState 中移出,并且在某种程度上成为了转换器堆栈中的同级。 Editor 不应该仍然是 AppState 的一部分,因为修改它意味着修改整体状态吗?

如何使用 AppStateEditor 正确初始化我的 Session,然后从我的 main 中初始化 运行?

how I can actually kick-start my transformer stack from my main function?

main =
  flip evalStateT initialAppState $
  flip evalStateT initialEditorState $
  eventLoop
  where
    initialAppState =
      error "Define me"
    initialEditorState =
      error "Define me"

Shouldn't Editor still be part of AppState because modifying it means modifying the overall state?

视情况而定。

还记得 Monad Transformer 的目的是以临时方式扩展功能吗?我的意思是临时的,不重写现有的代码库,而是通过添加到它。因此,如果您已经隔离了 API 的 Editor 和 AppState,则使用转换器堆栈将它们组合到另一个 "dome" 模块中会更容易。

OTOH,从初始架构的角度来看,AppState 是一个包含 Editor(我将其命名为 EditorState)等内容的数据结构是完全有道理的。在这种情况下,AppState 的API 应该封装Editor 的API。 "lens" 库将帮助您处理此类复合数据结构(尽管我必须提到它的学习曲线很陡)。