【发布时间】:2010-12-28 06:16:10
【问题描述】:
一直让我困惑的一件事是现在是否是使用 IORef 的好时机。在决定是否将 IORef 用于任务时,是否应遵循任何准则?什么时候是在 IORef 上使用 State monad 的好时机?
【问题讨论】:
标签: haskell state monads ioref
一直让我困惑的一件事是现在是否是使用 IORef 的好时机。在决定是否将 IORef 用于任务时,是否应遵循任何准则?什么时候是在 IORef 上使用 State monad 的好时机?
【问题讨论】:
标签: haskell state monads ioref
State 及其相关的 ST 都产生可以作为单元运行的“单片”有状态计算。他们基本上将可变状态视为产生结果所必需的中间数据,但其本身不应引起程序的其余部分的兴趣。
另一方面,放置在 IORef 中的不是要运行的“计算”——它只是一个包含简单值的框,可以在 IO 中以相当任意的方式使用。这个盒子可以放在数据结构中,在程序的(IO部分)周围传递,在方便的时候替换它的内容,被一个函数封闭等等。事实上,变量的很多杂乱性质和像 C 这样的语言的指针可以用 IORefs 建模,为任何希望维护他/她能够用任何语言编写 C 代码的声誉的专业 C 程序员提供极大的帮助......这绝对是要小心使用的东西。
尽管如此,有时非常难以使用,如果不是完全不可能的话,在单个代码块中隔离与一个可变状态的所有交互 - 一些状态必须简单地传递周围,放入数据结构等。在这种情况下,盒子方法可能是唯一的选择。在 48 小时内编写自己的计划教程(顺便说一下,强烈推荐)的 chapter introducing mutable state 提供了一个示例。 (有关为什么在特定的 Scheme 解释器设计中使用 IORefs 而不是 State 或 ST 来对 Scheme 环境进行建模的真正最合适的讨论,请参阅链接。)
简而言之,这些环境需要以任意方式嵌套,在用户交互实例之间进行维护(在 Scheme REPL 中键入的 (define x 1) 应该会导致用户稍后能够键入 x 并返回1 作为值),放入对象建模 Scheme 函数(因为 Scheme 函数关闭了它们创建的环境)等。
总而言之,我想说如果一项任务看起来非常适合它,State 将倾向于提供最干净的解决方案。如果需要多个单独的状态,也许 ST 可以提供帮助。但是,如果有状态计算难以使用或无法锁定在自己的代码段中,则状态需要在复杂程序的大部分生命周期中以可修改的形式持续存在等,那么 IORef 可能只是合适的东西。
再一次,如果需要那种可以通过 IO 代码以受控方式传递和交互的可变状态,为什么不检查一下 STM 及其 TVar!它们在存在并发的情况下要好得多,事实上,以至于解决一些与并发相关的任务实际上很简单。不过,这与问题并没有真正的关系,所以我会拒绝详细说明。 :-)
【讨论】:
就个人而言,我认为当且仅当您已经在使用IO 时使用IORefs 是可以的。否则,总是State,除非您需要ST 的卓越性能。可以通过 State monad 使用多个状态线程,以及一些辅助函数 - 您只需将状态设为元组或记录,并定义函数来分别设置、获取或更新每个字段。
特别是,使用StateT s IO 通常没有多大意义。如果你已经在IO,你已经有了可变状态,所以你不妨使用它——例如ReaderT (IORef s) IO。
【讨论】:
StateT s IO 是一个显而易见的解决方案,现在明显逊色。
State 和 ST 之间没有区别,ST 状态被“就地”修改了吗?
State 和 ST 是不同的,但它们都能够解决本质上相同的问题。
StateT s IO 是有充分理由的。特别是,编译器在优化它方面通常比在优化IORef 使用方面要好得多。例如,您很有可能将Int 状态用StateT Int IO 拆箱,但如果您想要一个真正的拆箱可变Int,则必须将IORef 替换为PrimArray 或类似的。
嗯。当您需要一些可变状态但处于单线程环境中时,您将使用 IORef。或者,当您想要一个更大的结构中的可变字段,而该结构又由同步变量保存时。
一般来说,使用 MVar。它们具有更强大的语义。
【讨论】:
当状态被本地化并且不需要与环境交互时,我使用STRef。
【讨论】: