反转状态单子的真实且有用的例子

12

反向状态monad是Haskell语言表达能力和惰性求值的非常棒而令人震撼的例子。但是理解这个monad并不容易。此外,很难找到一些令人信服的现实生活示例,说明你可以使用反向状态monad比语言中的任何其他工具更容易地完成某些任务。

反向状态 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回答:解释得很好,甚至有使用的现实生活例子,但没有代码,并且不清楚使用RState是否是一个真正好的想法。
  2. Mindfuck: 介绍了这个好的概念,但示例不是很有用。没有人会以这种方式编写斐波那契数。
  3. Kwang的Haskell博客:展示了如何使用RState来模拟Writer,但这不是一个真实的生活例子 :)

我也知道tardistardis,但这个库没有教程,文档示例非常抽象,没有多少人真正理解它。最接近我想要的是这个教程,但它有tardis的示例,而不仅仅是RState。还有这本书的参考资料

因此,我不是在寻找tardis的现实模式,我只对RState的说明感兴趣(如果可能的话)。虽然我明白可能没有纯粹使用RState的示例。在这种情况下,带有RStateT转换器或tardis的最小示例已经足够了。

是否有人在生活中使用过这个单子,或者有非常好的有用代码示例?


1
你的帖子似乎是反转了 Stack Overflow 的格式。你已经有了一个答案,现在在寻找问题。这个网站是为了解决实际问题而存在的,而不是广泛的、推测性的问题。 - 4castle
12
或许你是对的,但至少我认为这个话题足够令人兴奋,可以说,在这种情况下,“谁在乎 Stack Overflow 的格式”不重要。 - leftaroundabout
12
为什么所有事物都必须有现实世界的用途?它们不能只是酷、好玩或美丽吗? - Benjamin Hodgson
3
@BenjaminHodgson 事物可以很酷、很美丽、很有趣,当然。但是,将一个仅仅是美丽的东西应用到现实世界中并让它发挥作用,这种乐趣是独一无二的!如果你从各个角度去看待一个问题,例如通过现实世界的例子来思考,你会更深入地理解它。 - Shersh
3
这个单子的一个版本被用来实现Data.Traversable.mapAccumR - dfeuer
显示剩余2条评论
1个回答

8

我已经了解这些单子(monads)十多年了,最近才看到了它们的一个实际应用。这是在一个有点不寻常的设置中。我和一个同事正在使用“reflex”库进行函数响应编程,并正在开发一个帮助构建终端图形应用程序的库。如果您熟悉“reflex-dom”,它的性质类似,只是我们的基本单子不是将后续小部件依次放置在DOM中,而是将基于终端字符单元的“图像”堆叠在一起,并由用户合理地划分屏幕。我们想提供一些比这更好的东西,可以在某种程度上跟踪剩余的屏幕空间,并允许用户将一些“瓷砖”按行和列放置,以便do-block基本上对应于屏幕上的一列或一行瓷砖。

除了处理布局问题外,我们还希望这些瓷砖能够管理键盘焦点,允许用户按Tab键循环浏览它们,或按Shift-Tab键反向浏览。正是在这里,前进和后退状态单子变换器变得非常方便:我们可以将当前状态向任何一个方向设置为事件(空元组的事件)。每个瓷砖都可以向前一个和后一个小部件发送事件(并从它们接收事件),通知小部件它们何时接收到键盘焦点,因此应该停止阻止按键到达其子小部件。因此,这个瓷砖小部件的原理图大致如下:

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是普通状态的“放置”,sendToPast是时光倒流的“放置”,recvFromPast是普通状态的“获取”,而recvFromFuture则是时光倒流的“获取”。因此,focusP :: Event t ()是我们从前一个元素(可能是另一个与此类似的瓷砖)那里得到的事件,告诉我们现在我们有了焦点,而focusF则是我们从后继元素那里接收到的类似事件。我们使用'hold'跟踪我们何时拥有焦点,以构建focused :: Behavior t Bool,然后用它来门控键盘事件,以确保只有在我们自己有焦点时才告诉邻居他们正在接收焦点,并且也用于我省略的部分,其中我们正在运行子窗口小部件,以适当地过滤其输入事件。
我不确定我们到发布库时是否仍会按照这种方式进行,但到目前为止,它似乎运作良好,我很高兴终于注意到了这种构造可以被实际使用的情况。

真棒的例子! - Shersh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接