在FRP中实现快照

6
我正在Scala中实现一个FRP框架,似乎遇到了问题。出于某种思考的动机,我决定限制框架的公共接口,使得Behaviours只能在“present”时被评估,即:
behaviour.at(now) 

这也符合Conal在Fran论文中的假设,即Behaviours只会在递增的时间点进行评估/采样。这确实限制了对Behaviours的变换,但否则我们会在代表某些输入的Behaviours方面遇到巨大问题:

val slider = Stepper(0, sliderChangeEvent) 

使用这种行为,评估未来值将是不正确的,并且评估过去的值需要一个无限数量的内存(所有在“slider”事件中使用的出现都必须被存储)。

考虑到这个限制,我对 Behaviours 的 'snapshot' 操作规范有困难。我最好通过一个示例来解释我的问题(使用上面提到的滑块):

val event = mouseB // an event that occurs when the mouse is pressed 
val sampler = slider.snapshot(event) 
val stepper = Stepper(0, sampler) 

我的问题在于,如果在执行此代码时发生了'mouseB'事件,那么'stepper'的当前值将是'slider'的最后一个'sample'(即上次事件发生时的值)。如果上次事件发生的时间已经过去,那么我们将会使用过去的时间来评估'slider',这将违反上述规则(和你最初的假设)。我可以想到几种解决方法:
  1. 记录过去的值(保留所有事件的过去值),允许使用过去的时间评估行为(需要无限制的内存)
  2. 修改'snapshot'函数,增加一个时间参数(“在此时间之后采样”),并强制要求该时间大于等于现在
  3. 更疯狂的做法是,我们可以以某种方式限制FRP对象的创建只能在程序的初始设置中完成,并且只有在此设置完成后才开始处理事件/输入。
我也可以简单地不实现“sample”,或者删除“stepper”/“switcher”(但我不想这样做)。有人对此有什么想法吗?我是否误解了什么?

1
你知道Reactive吗? - Daniel C. Sobral
2
响应式编程很酷,但它打破了FRP中的一些概念。例如,它没有连续行为的概念:在响应式中,信号在不同值之间在时间上离散地变化。我最初对这个如何适用于FRP感到困惑,并在一段时间前提出了这个问题:https://dev59.com/Cms05IYBdhLWcg3wGuKd - seadowg
此外,据我所知,响应式编程并没有像“快照”这样的功能。 - seadowg
3个回答

3
据我所知,您正在担心一种竞态条件:即在代码执行期间发生事件会发生什么。
纯函数式代码不喜欢必须知道它何时被执行。在纯净的环境中,功能技术最为重要,因此代码的执行顺序并不重要。摆脱这种困境的方法是假装每个更改都发生在一个敏感的(内部的,可能的)命令式代码片段中;假装FRP框架中的任何函数声明都是0时间发生,因此在其声明期间不可能发生任何更改。
永远不应该在声明行为和事物的代码部分中睡觉或进行真正的时间敏感操作。本质上,与FRP对象一起工作的代码应该是纯净的,那么就不会有任何问题。
这并不一定排除在多个线程上运行它,但为了支持它,您可能需要重新组织内部表示。欢迎来到FRP库实现的世界 - 我猜测在此过程中您的内部表示将会多次波动。 :-)

有趣的一点。我不担心在执行此代码时是否会发生某些事情(所有这些可能是问题),但如果 mouseB 在之前发生,那就不同了。简单来说,鉴于“快照”的定义,在返回事件中的“最后”发生将是(t,slider.at(t)),其中 t < now。 - seadowg

3

哦,我现在理解你的意思了。

我认为你对“只能在‘现在’进行采样”的限制并不够严格。它需要更加强制,以避免查看过去。由于你使用了环境概念中的now,我会根据它来定义行为构造函数(只要now不能因执行定义而提前,否则会变得混乱,这是我的上一个答案中提到的)。例如:

Stepper(i,e) 是一个行为,在区间[now,e1](其中e1now后第一次出现e的时间)内具有值i,之后具有最近一次出现e的值。

通过这种语义,导致你陷入困境的对stepper值的预测已被瓦解,步进器将现在具有值0。我不知道你是否满意这种语义,但对我来说似乎很自然。


这是一种非常好的展示方式。我只是在尝试找到一种在不使用无限量内存的情况下保持原始(可爱的)语义的方法时感到沮丧。我注意到有些实现会因此删除“stepper”和“switcher”,但它们似乎非常强大。唉,算了。 - seadowg
@seadowg,Fran语义很好,但我们目前未能有效地实现它们(它们总是存在空间泄漏和相应的缓慢)。我个人花了大约6个月的时间解决这个问题。我们也无法确定任何原因,说明它不可能,因此仍然可能存在解决方案。可以说这并不明显。 - luqui
1
@luqui:在这篇博客文章中,Heinrich Apfelmus指出了一篇论文,据称该论文解释了为什么标准的动态事件切换类型本质上是错误的。我不是足够专家来理解它,但你看过这个吗?如果是,你觉得它有说服力吗?(有点离题,但我认为在我看到它时,这在FRP社区应该是众所周知的。) - ehird
@ehird:基本上,这篇论文(实际上是幻灯片)认为FRP对应于一种类型系统中的逻辑,即时态逻辑。这非常美妙。但为了使其起作用,您必须添加一个额外的类型参数,然后您会发现switcher的原始类型不合适。 - Heinrich Apfelmus
说实话,这并不明显。只有回过头来看才能明白。 - Conal

0

我对你的困惑感到困惑。我认为,每当事件发生时,Stepper将“设置”行为为新值。所以,发生以下情况:

事件mouseB发生的瞬间,将读取slider行为的值(snapshot)。该值将被“设置”到stepper行为中。

因此,Stepper确实会“记住”过去的值;关键是它只记住了过去的最新值,而不是所有值。

从语义上讲,最好像luqui建议的那样将Stepper建模为一个函数。


如果我们不允许过去的采样,是的。我的问题是,如果我们在使用具有过去发生事件的行为时调用快照,那么我们将使用过去的时间对该行为进行采样(我不想允许这种情况)。Stepper被用来辅助示例。 - seadowg
@seadowg:啊,我明白了。你有两个选择:要么确保过去的某个时刻已经成为现在,也就是说你已经“走过了过去的事件”。要么你必须修剪你刚刚意识到的过去事件。这些是避免时间泄漏(记住过去无限量的数据)的唯一方法。总的来说,积累和动态事件切换不能自由组合,必须有一些限制。 - Heinrich Apfelmus

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