使用 StateT
monad变换器,我可以创建类型 StateT s [] a
,它与 s -> [(a, s)]
同构 . 现在我宁愿使用STT monad transformer,因为我希望有多个不同类型的可变变量,并且希望能够随意实例化它们,具体取决于早期计算的结果 .
但是, STT
的链接文档明确提到:
此monad变换器不应与包含多个答案的monad一起使用,例如list monad . 原因是状态令牌将在不同的答案中重复,这会导致坏事发生(例如参考透明度丢失) . 安全monad包括monads State,Reader,Writer,Maybe和他们相应的monad变换器的组合 .
那么我的选择是什么?
要完全清楚:
-
我所追求的是非决定论 . 我希望能够分叉我的计算,为每个分支提供整个状态的副本 .
-
我对并行性并不在意,因为性能不是我最关心的问题 .
-
我不追求的是并发:不同的计算分支不应该共享可变变量;相反,它们应该都在自己的原始可变变量的副本上工作 .
编辑:我已经意识到 STT
monad变换器的行为与 StateT
的行为本质上是不安全的 . 有了它,我们可以构建一个 STT sloc (ListT (ST sglob)) a
类型 . 这里, sglob
是全局状态的名称,而 sloc
是本地状态的名称 . *现在我们可以使用全局状态在线程之间交换本地状态引用,从而可能获得对未初始化变量的引用 .
*为了进行比较,相应的 StateT
构造是 StateT sloc (ListT (State sglob)) a
,它与 sloc -> sglob -> ([(a, sloc)], sglob)
同构 .
2 回答
你不会绕过
StateT
,因为对于这种非确定性的东西,编译器需要知道哪些“变量”需要分支出来 . 当变量可能潜伏为STRef
时,这是不可能的 .要仍然获得“不同类型的多个变量”,您需要将它们打包在合适的记录中,并将其用作单个实际状态变量 . 处理这样的状态对象似乎很尴尬?嗯,使用镜头访问“个体变量”并没有那么糟糕 .
(输出
12
) .“能够随意实例化它们”有点棘手,因为只有通过更改状态对象才能添加变量,这意味着你不再真正处于同一个monad中 . 镜头具有zooming的概念,可以使用 - 将状态对象拆分为“范围”并使用计算,其中只有一些变量被定义为放大到该范围 .
为了使这非常方便,您需要可以随意扩展的记录 . 我真的很喜欢Nikita Volkovs record library approach,这似乎最近没有进一步推进 . Vinyl也是朝这个方向发展的,但我没有深入研究过 .
在未来,我们将拥有OverloadedRecordFields extension,这将有助于这种东西 .
不推荐这个答案,请参阅the other one .
为了扩展您的想法以使用弱类型的变量映射包装
StateT
,这将类似于以下内容:我不相信这实际上会很聪明 . 这些
STT'Ref
不是垃圾收集的 . 因此,如果您在循环中运行使用newSTTRef
的操作,它将在每次迭代中实际增长IntMap
,而不会释放已经“超出范围”的变量(即,没有任何引用指向它们) .可能会为所有这些添加一个实际的垃圾收集器,但这会使它变得非常复杂 .