Reverse State monad 的现实生活和有用的例子

Real life and useful examples of Reverse State monad

Reverse State monad 是 Haskell 语言的表现力和懒惰求值的非常好的和令人兴奋的例子。但是要理解这个 monad 并不是那么容易。此外,很难找到一些令人信服的现实生活示例,说明使用 Reverse State monad 比使用该语言中的任何其他工具更容易做什么。

Reverse State monad 定义如下:

newtype RState s a = RState { runRState :: s -> (a,s) }

instance Monad (RState s) where
    return x = RState $ (,) x
    RState sf >>= f = RState $ \s ->
        let (a, past)   = sf future
            (b, future) = runRState (f a) s
        in (b, past)

它已经有一些示例和用法,但我觉得它们不太实用。

  1. Quora answer:解释得很好,甚至有现实生活中的用法示例,但没有代码,不清楚使用 RState.
  2. 是否是个好主意
  3. Mindfuck: 介绍了这个不错的概念,但示例没有用。没有人会这样写斐波那契数列。
  4. Kwang's Haskell Blog:展示了如何用 RState 模拟 Writer,但是加油。不是真实生活中的例子:)

我也知道 tardis package but no tutorial of this library, documentation examples are really abstract, not so many people really understand it. The closest to what I want is this tutorial but it has example of tardis, not just RState. As well as this book reference

因此我不是在寻找 tardis 真实的生活模式,如果可能的话,我只对 RState 插图感兴趣。虽然我知道可能没有纯 RState 用法的样本。在那种情况下,使用 RStateT 变压器或 tardis 的最小示例就足够了。

有人在现实生活中使用过这个 monad 还是有非常好的和有用的代码说明?

我已经知道这些 monad 十多年了,直到最近才看到它们的实际应用。它的环境有点不寻常。我和一位同事正在通过 'reflex' 库使用函数式反应式编程,并且正在开发一个库来帮助构建终端图形应用程序。如果你熟悉 'reflex-dom',它在本质上是相似的,除了我们的基本 monad,而不是将后续的小部件一个接一个地放在 DOM 中,而不是只是堆叠终端字符单元格 "images" 在彼此之上,由用户明智地划分屏幕。我们想提供比这更好的东西,它会在某种程度上跟踪剩余的屏幕空间,并让用户在行和列中放置一些 "tiles",这样一个 do-block 基本上对应于屏幕上的一列或一行图块。

除了处理布局问题外,我们还希望图块能够管理键盘焦点,允许用户按 Tab 键循环浏览它们,或按 Shift-Tab 键反向移动。正是在这里,时间向前和向后的状态 monad 转换器变得非常方便:我们可以让任一方向的当前状态成为一个事件(一个空元组)。每个 tile 都可以向前一个和下一个小部件发送一个事件(并从它们接收一个事件),在小部件接收到键盘焦点时通知小部件,因此应该停止阻止按键到达它们的子小部件。如此示意性地,磁贴小部件看起来像:

do rec focusP <- recvFromPast
       sendToPast shiftTabPress
       tabPress <- gate focused $ ... filter input Event for Tab keypresses ...
       shiftTabPress <- gate focused $ ... filter input Event for Shift-Tab ...
       focused <- hold False $ leftmost
         [ True <$ (focusP <> focusF)
         , False <$ (shiftTabPress <> tabPress) ]
       v <- ... run the child widget and do layout stuff ...
       sendToFuture tabPress
       focusF <- recvFromFuture
   return v

这里sendToFuture是普通状态"put",sendToPast是逆时"put",recvFromPast是普通状态"get",recvFromFuture是逆时"get".所以 focusP :: Event t () 是我们从我们的前任(可能是另一个像这样的磁贴)那里得到的事件,告诉我们我们现在有焦点,而 focusF 是我们从我们的继任者那里收到的类似事件。我们使用 'hold' 构造 focused :: Behavior t Bool 来跟踪我们何时获得焦点,然后使用它来控制键盘事件,这样我们就可以确保告诉我们的邻居他们只有在以下情况下才会获得焦点we ourself are focused,并且在我省略的位中也使用了我们 运行 子窗口小部件,以便适当地过滤其输入事件。

我不确定在库发布时我们是否真的会继续这样做,但到目前为止它似乎运行良好,我很高兴终于注意到一个案例这种结构可以投入实际使用。