使用 StateT
monad 转换器,我可以创建类型 StateT s [] a
, 与 s -> [(a, s)]
同构.现在我更喜欢使用 STT
monad transformer相反,因为我希望拥有多个不同类型的可变变量,并且希望能够根据早期计算的结果随意实例化它们。
但是,STT
的链接文档明确提到:
This monad transformer should not be used with monads that can contain multiple answers, like the list monad. The reason is that the state token will be duplicated across the different answers and this causes Bad Things to happen (such as loss of referential transparency). Safe monads include the monads State, Reader, Writer, Maybe and combinations of their corresponding monad transformers.
那么我的选择是什么?
要完全清楚:
编辑:
(编辑编辑:以下反例无效,因为
ListT
不应应用于非交换单子(monad) ST
和 State
。)我开始意识到
STT
monad 转换器,其行为符合 StateT
本质上是不安全的。有了它,我们可以构建一个类型 STT sloc (ListT (ST sglob)) a
.在这里,sglob
是全局状态的名称,而 sloc
是本地州的名称。*现在我们可以使用全局状态在线程之间交换局部状态引用,从而有可能获得对未初始化变量的引用。
*为了比较,对应的
StateT
build 是StateT sloc (ListT (State sglob)) a
, 与 sloc -> sglob -> ([(a, sloc)], sglob)
同构.
最佳答案
你不会绕过StateT
,因为对于这种不确定性的东西,编译器需要始终知道哪些“变量”需要被分支出来。当变量可能潜伏在 STRef
的任何地方时,这是不可能的。 s。
要仍然获得“不同类型的多个变量”,您需要将它们打包到合适的记录中,并将其用作单个实际状态变量。处理这样的状态对象似乎很尴尬?嗯,使用镜头访问“个体变量”并没有那么糟糕。
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Data.Monoid
import Control.Monad.Trans.State
import Control.Monad.ListT
import Control.Monad.Trans.Class
import Control.Monad.IO.Class
data Stobjs = Stobjs {
_x :: Int
, _y :: String
}
makeLenses ''Stobjs
main = runListT . (`runStateT`Stobjs 10 "") $ do
δx <- lift $ return 1 <> return 2 <> return 3
xnow <- x <+= δx
y .= show xnow
if xnow > 11 then liftIO . putStrLn =<< use y
else lift mempty
(输出
12
)。“能够随意实例化它们”有点棘手,因为只有通过更改状态对象才能添加变量,这意味着您将不再真正处于同一个 monad 中。镜头有 zooming 的概念可以使用它——将状态对象拆分为“范围”并使用计算,其中只有一些变量被定义为放大到该范围。
为了使这真正方便,您需要可以随意扩展的记录。我真的很喜欢 Nikita Volkovs
record
library approach ,这似乎最近没有进一步发展。 Vinyl也朝那个方向发展,但我没有深入研究。future ,我们将拥有
OverloadedRecordFields
extension这将有助于解决这类问题。
关于haskell - 在 Haskell 中结合 ST 和 List 单子(monad),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51874362/