【问题标题】:Reactive's Monad and Applicative instances in Conal Elloit's FRPConal Elloit 的 FRP 中 Reactive 的 Monad 和 Applicative 实例
【发布时间】:2019-11-26 12:54:01
【问题描述】:

Conal Elliott's paper 有以下定义:

Future a = (Time, a)
Stepper :: a -> Event a -> Reactive a
Ev :: Future (Reactive a) -> Event a
never : Event a
instance Monad Reactive
rA :: Reactive String
rA = "IA" `Stepper` (Ev (1, "1A" `Stepper` never))

rB :: Reactive String
rB = "IB" `Stepper` (Ev (1, "1B" `Stepper` never))

rC1 :: Reactive String
rC1 = (++) <$> rA <*> rB
rC1 = "IAIB" `Stepper` (Ev (1, "IA1B" `Stepper` never))

我相信以上是正确的。

rC2 :: Reactive String
rC2 = rA >>= (\a -> (a++) <$> rB)

应该rC1 = rC2吗?

根据论文中的定义,"IA1B" and "1AIB" 似乎将包含在rC2 中的"IAIB""1A1B" 之间。

这不违反单子定律(&lt;*&gt;) = ap 吗?不应该rC1 = rC2吗?或者我误解了什么。

Here is the Idris code I am using

【问题讨论】:

  • 你认为这违反了哪条单子法则? monad 定律没有告诉我们任何关于&gt;&gt;=&lt;*&gt; 之间关系的信息,只是关于单子操作的结构(只是&gt;&gt;=return
  • @trpnd (&lt;*&gt;) = ap where ap m1 m2 = m1 &gt;&gt;= (\x1 -&gt; m2 &gt;&gt;= \x2 -&gt; return (x1 x2))
  • @trpnd,它实际上在 iirc 文档中被列为 Applicative law,但我们通常确实需要 monad 和 Applicative 之间的一致性
  • 从技术上讲,这更像是一种惯例而不是法律。尽管在违反它的情况下,预计在某种意义上这两个事物是等价的,即使不完全相同。例如,查看 haxl 之类的内容,其中 (&lt;*&gt;) 可以并行运行,但 ap 不能。
  • 看起来我们仍然应该只在rC2 中获得 2 个值,因为 monad 选择第一个到达的未来并丢弃另一个。你是如何计算rC2的?你能展示你的作品吗? (我没有一定怀疑你,我只需要查看更多细节并且我在手机上无法自己进行计算。)

标签: haskell frp


【解决方案1】:

这个故事有两个错误。问题给出的例子实际上只暴露了第一个,但在处理它时,我在join的定义中发现了另一个更严重的错误。

第一个错误是次要的:它与处理同时发生的事件有关。您的race 函数允许您合并同时发生的事件。这在(&lt;*&gt;) 中得到了利用,但是在您对join 的定义中,These 的情况假设左侧(内部)事件首先发生。所以如果你想用join实现(&lt;*&gt;),并且这两个行为有同时发生的事件,join不会合并它们。

在反应行为的情况下,两个同时发生的事件与两个发生在时间上非常接近的事件是无法区分的,无论如何,最后一个获胜,所以前一个是否出现在流与否。例如,如果您开始计算事件,这将崩溃,并且据此我会简单地得出结论,不应允许对行为进行此类操作。

我认为出现在法律中的= 符号经常被采用句法(如果你允许我这么称呼它的话),而在各种情况下,比如这个,它只是语义上更灵活地阅读它。它们经常是一致的,所以除非你习惯于正式地思考它,否则也很难真正理解这一点。

从这个角度来看,将其视为“不是错误”是合理的。似乎仍然可以修复实现,以便法律在语法上确实成立;无论如何,这会让每个人都开心。


要理解第二个bug,你必须再看一下join :: Reactive (Reactive a) -&gt; Reactive a的含义。

r :: Reactive (Reactive a) 是一种不断演变的行为。有时你会看到行为x(未显示其内部变化),然后是行为y,然后是行为z......所以从示意图上看,它看起来像一个行为流

xxxxx...
yyyyy...
zzzzz...
...

如果我们将行为视为时间的函数r :: Time -&gt; (Time -&gt; a)join 在时间r 采样t,那是另一种行为r t :: Time -&gt; a,它本身在时间t(join r) t = r t t 采样。换句话说,你采取对角线行为:

x
 y
  z

因此,如果我们回顾Reactive 的事件驱动定义,一旦出现新行为就会很自然地忘记事件。事实上,论文中出现的join 的定义是通过与join &lt;$&gt; urr 竞争来实现的,它忘记了ur 的内部行为。然而,这只占对角线的一侧:

x
yy
zzz

因为Reactive 行为是事件流,Reactive (Reactive a) 的内部流可以包含在流自身出现之前发生的事件。因此,当您获得Future (Reactive a) 时,您还需要一种方法来截断Future 发生之前的事件:

actualize :: Future (Reactive a) -> Future (Reactive a)
-- Exercise for the reader

并在 join 的某个地方使用它。

【讨论】:

  • 使用actualize给了我想要的行为
  • 随意编辑我的答案以显示加入的正确定义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-30
  • 2014-06-14
  • 1970-01-01
  • 2011-11-05
  • 2012-11-12
相关资源
最近更新 更多