在 Haskell 中结合 ST 和 List monad

dre*_*ris 5 haskell non-deterministic state-monad st-monad

使用StateTmonad 转换器,我可以创建与StateT s [] a同构的类型s -> [(a, s)]。现在我更喜欢使用STTmonad 转换器,因为我想要多个不同类型的可变变量,并且希望能够根据早期计算的结果随意实例化它们。

但是,STT明确提及的链接文档:

这个 monad 转换器不应该与可以包含多个答案的 monad 一起使用,比如列表 monad。原因是状态标记将在不同的答案中重复,这会导致坏事发生(例如失去参考透明度)。安全的 monad 包括 monads State、Reader、Writer、Maybe 以及它们对应的 monad 转换器的组合。

那么我的选择是什么?

完全清楚:

  • 我所追求的是非确定性。我希望能够分叉我的计算,为每个分支提供自己的整个状态副本。
  • 我不太介意并行性,因为性能不是我最关心的问题。
  • 追求的是并发:不同的计算分支不应共享可变变量;相反,他们都应该处理他们自己的原始可变变量的副本。

编辑:(编辑编辑:下面的反例是无效的,因为ListT不应该应用于非可交换的 monadSTState。)我开始意识到按照 的方式STT行事的monad 转换器StateT本质上是不安全的。有了它,我们可以构建一个类型STT sloc (ListT (ST sglob)) a。这里,sglob是全局状态sloc的名称,而是局部状态的名称。* 现在我们可以使用全局状态在线程之间交换局部状态引用,从而潜在地获得对未初始化变量的引用。

*为了比较,对应的StateT构造是StateT sloc (ListT (State sglob)) a,与 同构sloc -> sglob -> ([(a, sloc)], sglob)

lef*_*out 4

你不会绕过StateT,因为对于这种不确定性的东西,编译器需要始终知道哪些 \xe2\x80\x9cvariables\xe2\x80\x9d 需要分支。当变量可能像STRefs 一样潜伏在任何地方时,这是不可能的。

\n\n

为了仍然获得\xe2\x80\x9c不同类型的多个变量\xe2\x80\x9d,您需要将它们打包在合适的记录中并将其用作单个实际状态变量。处理这样的状态对象似乎很尴尬?好吧,当使用镜头访问\xe2\x80\x9单个变量\xe2\x80\x9d时,情况并没有那么糟糕。

\n\n
{-# LANGUAGE TemplateHaskell #-}\n\nimport Control.Lens\nimport Data.Monoid\n\nimport Control.Monad.Trans.State\nimport Control.Monad.ListT\nimport Control.Monad.Trans.Class\nimport Control.Monad.IO.Class\n\ndata Stobjs = Stobjs {\n    _x :: Int\n  , _y :: String\n  }\n\nmakeLenses \'\'Stobjs\n\nmain = runListT . (`runStateT`Stobjs 10 "") $ do\n   \xce\xb4x <- lift $ return 1 <> return 2 <> return 3\n   xnow <- x <+= \xce\xb4x\n   y .= show xnow\n   if xnow > 11 then liftIO . putStrLn =<< use y\n                else lift mempty\n
Run Code Online (Sandbox Code Playgroud)\n\n

(输出12)。

\n\n

\xe2\x80\x9c能够随意实例化它们\xe2\x80\x9d 有点棘手,因为添加变量只能通过更改状态对象来实现,这意味着你将不再真正处于同一个 monad 中。Lens 具有缩放的概念,可以使用 \xe2\x80\x93 将状态对象拆分为 \xe2\x80\x9cscopes\xe2\x80\x9d 并使用仅部分变量定义为放大的计算那个范围。

\n\n

为了使这真正方便,您需要可以随意扩展的记录。我真的很喜欢 Nikita Volkovsrecord库方法,这最近似乎没有进一步进展。乙烯基塑料也朝这个方向发展,但我还没有深入研究过。

\n\n

将来,我们将提供有助于解决此类问题的OverloadedRecordFields扩展。

\n