5

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

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

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

那么我的选择是什么?

要完全清楚:

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

编辑:(编辑编辑:以下反例无效,ListT不应应用于非交换单子和STState)我开始意识到,STT按照 StateT有了它,我们可以构建一个 type STT sloc (ListT (ST sglob)) a。这里,sglob是全局状态sloc的名称,而是局部状态的名称。* 现在我们可以使用全局状态在线程之间交换局部状态引用,从而有可能获得对未初始化变量的引用。

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

4

2 回答 2

4

你不会绕过StateT,因为对于这种不确定性的东西,编译器需要始终知道哪些“变量”需要被分支出来。当变量可能潜伏在STRefs 的任何地方时,这是不可能的。

要仍然获得“不同类型的多个变量”,您需要将它们打包到合适的记录中,并将其用作单个实际状态变量。处理这样的状态对象似乎很尴尬?嗯,使用镜头访问“个体变量”并没有那么糟糕。

{-# 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 中。Lens 具有可以使用的缩放概念——将状态对象拆分为“范围”,并使用仅将一些变量定义为放大到该范围的计算。

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

将来,我们将拥有可以帮助解决此类问题的OverloadedRecordFields扩展程序。

于 2018-08-16T11:00:12.973 回答
2

这个答案不推荐,看另一个


为了扩展你的想法,StateT用弱类型的变量映射来包装 a,这看起来像下面这样:

{-# LANGUAGE GADTs #-}

import Unsafe.Coerce
import Data.IntMap

data WeakTyped where
   WeakTyped :: a -> WeakTyped

newtype STT' m a = STT' { weakTypState :: StateT (IntMap WeakTyped) m a }
  deriving (Functor, Applicative, Monad)

newtype STT'Ref a = STT'Ref { mapIndex :: Int }

newSTTRef :: Monad m => a -> STT' m (STT'Ref a)
newSTTRef x = STT' $ do
   i <- (+1) . maximum . keys <$> get
   modify $ insert i x
   return $ STT'Ref i

readSTTRef :: Monad m => STT'Ref a -> STT' m a
readSTTRef (STT'Ref i) = STT' $ do
   unsafeCoerce . (!i) <$> get

我不相信这实际上会很聪明。Haskell 运行时没有正确处理这些STT'Refs,特别是状态变量不会被垃圾收集。因此,如果你运行一个newSTTRef在循环中使用的动作,它实际上会IntMap在每次迭代中增长,而不会释放已经“超出范围”的变量(即没有任何指向它们的引用)。

可能会为所有这些添加一个实际的垃圾收集器,但这会使它变得非常复杂。

于 2018-08-16T12:00:46.567 回答