功能响应式编程语言规范

Posted

技术标签:

【中文标题】功能响应式编程语言规范【英文标题】:Specification for a Functional Reactive Programming language 【发布时间】:2011-08-18 01:40:46 【问题描述】:

我正在考虑在某个时候创建​​一个功能性反应框架。我已经阅读了很多关于它的内容并看到了一些示例,但我想清楚地了解这个框架必须做什么才能被视为 FRP 扩展/dsl。我并不真正关心实施问题或细节等,而是更关心在完美世界情况下需要什么。

理想的函数式反应式编程语言的关键操作和品质是什么?

【问题讨论】:

出于好奇,您最终创建了框架吗? 【参考方案1】:

我假设您可能已经在功能 I/O 上看到了 Matthias Felleisen’s talk 并阅读了 his paper。我认为他是一个非常务实和美丽的方法。希望您也偶然发现了Conal Elliott 的一些出色作品。

我个人的要求是系统是完全纯净的。也就是说,所有行为都由纯world->world函数定义,所有实现或可视化都由world->visual函数定义;其中visual 是系统输出的一些静态描述。

我的另一个主要功能是历史调试器。维护world 状态的历史并能够从任何时间点重播应该是相对简单的。

一个非常有趣的研究领域(我相信一个未解决的问题)是使用连续时间,而不是在一些离散时钟滴答声上迭代 world->world 函数。我曾经做过few blog posts on FRP 和Conal Elliott 留下以下发人深省的评论:

我喜欢外延/功能 方法,用于可组合性和 语义清晰。对于相同的 原因,我更喜欢连续时间& 离散时间和空间上的空间。在 所有这些情况,越少 类似机器的配方很好 将什么与它的方式分开 基于机器的演示。

解决这个问题,你就会成为英雄!

【讨论】:

world -> world 模型有两个主要问题。首先,除非我感到困惑,否则该模型仅支持 sequential 组合,并且对 parallel 组合不友好。想象一下您如何并行组合两个world->world 值。其次,我无法看到它如何支持 continuous 时间。这两个问题都阻碍了可组合性,这是我在库设计中的一个关键目标。【参考方案2】:

很高兴您从询问规范开始,而不是首先询问实施。 关于 FRP 是什么,有很多想法。 从 90 年代初期开始(当时我在 Sun Microsystems 和 Microsoft Research 从事交互式图形工作),它大约有两个属性(a)表示性和(b)时间连续性。 许多人放弃了这两个属性,并使用各种实现概念来识别 FRP,在我看来,所有这些都是无关紧要的。 为了减少混淆,我希望看到术语“函数式反应式编程”被更准确和描述性的“指示性、连续时间编程”(DCTP)取代,正如 Jake McArthur 在a conversation last year 中所建议的那样。

我所说的“外延”是指建立在精确、简单、独立于实现的组合语义之上,该语义准确地指定了每种类型和构建块的含义。 然后,语义的组合性质决定了构建块的所有类型正确组合的含义。 对我来说,外延是函数式编程的核心和本质,是实现精确和易于推理的原因,因此是正确性、推导和优化的基础。 Peter Landin 建议将“外延”作为模糊术语“函数式”的实质性替代,以及一种将深度/真正函数式编程与仅仅具有函数式外观的符号区分开来的方法。 请参阅this comment 了解一些 Landin 引述和论文参考。

关于连续时间,请参阅帖子 Why program with continuous time? 以及我在此页面上 AshleyF 的回答中的引用。 考虑到计算机的离散性,听到关于连续时间的想法在某种程度上不自然或不可能实现的说法,我一次又一次地感到惊讶。 这种思路让我觉得很奇怪,尤其是来自 Haskellers 时,原因如下:

使用 lazy 函数式语言,我们在 finite 机器上随意使用 infinite 数据进行编程。因此,我们得到了可爱的模块化,正如 John Hughes 的经典论文 Why Functional Programming Matters 中所说明的那样。 有很多在连续空间中编程的示例,例如矢量图形,还有Pan 之类的东西。 我喜欢我的程序反映我对问题空间的看法,而不是执行程序的机器,我倾向于期望其他高级语言程序员也有这种偏好。 (“当程序需要关注不相关的内容时,编程语言是低级的。” - Alan Perlis)

自从TBAG 和ActiveVRML(第一个 DCTP/FRP 系统)和后来的Fran 以来,我一直在制作用于连续时间编程的库。 很容易正确实施。 论文Functional Implementations of Continuous Modeled Animation 中描述了几种不同的方法。 实现连续时间有效(并且仍然正确!)是另一回事,尤其是避免重新计算不变的值。 (见论文Push-pull functional reactive programming。)

有关相关评论,请参阅my answer to The difference between Reactive and Functional-Reactive programmingWhat is (functional) reactive programming? 更新:有关连续时间为何重要的更多信息,请参阅these notes。 更新:另请参阅我的 2015 年演讲 The essence and origins of FRP(以及链接的相关演讲)。

祝您探索顺利,如果您有任何问题,请告诉我。 我的联系方式是my home page。

【讨论】:

令我感兴趣的是连续函数的概念都是关于有限限制的。函数连续性的定义是:对于您想要输出的任何有限量的信息,您必须输入的信息量是有限的。因此,如果您使用的是连续时间,那么您只需要使用连续函数即可它是非常自然的。 @Bradford 知道了。据我所知,Rx 和 Bacon.js 缺少我作为原始 FRP 基础的两个基本属性中的两个。从这个意义上说,它们并不是 FRP 的初衷。这对我来说很好,因为我喜欢看到探索大量的抽象。然而,使用一个术语来描述它们会让人更加混淆它们的含义以及它们之间的区别。我认为对 Rx 和 Bacon.js 的准确描述是“受 FRP 启发的组合事件系统”。 这些系统是离散的,基于流,即由时间间隙分隔的可能无限的样本序列。相反,连续系统定义所有次的值,没有任何时间间隙。换句话说,离散系统的分辨率是有限的,而连续系统的分辨率是无限的。 另见Discrete time and continuous time。 @CMCDragonkai 我同意您的观点,即数字计算机中的时间离散性只是一种抽象,而不是机器的本质。我们可以轻松且正确地在数字抽象之上实现连续时间抽象,以及在有限之上实现无限。毕竟,连续和不可数的数学是在有限的字母表、推理系统和推理之上表达的。【参考方案3】:

好吧,除非完美的世界你指的是心灵感应计算机(哎呀!),那么你需要某种方式来处理用户我/O - 我假设像 orthogonal persistence 这样的东西已经包含了更无聊的 file I/O...

让我们从输入开始...因为它已经有了一个解决方案。来自 Conal Elliott 和 Paul Hudak 的开创性论文的第 4 页(共 11 页)Functional Reactive Animation:

磅; rbp : 时间 → 事件 事件 ( )

在 Haskell 中,它看起来像:

 -- read left and right mouse button-press events
lbp, rbp :: Time -> Event (Event ())

所以对于键盘输入:

kbd :: Time -> Event Char.

可以以类似的方式处理其他输入。

那么……输出呢?实际的词没有出现在论文的任何地方(“I/O”也没有出现)——我们必须自己弄清楚这个。但这一次,是我们的 Haskell 翻译:

lbp, rbp :: Time -> Event (Event ())

提供提示 - Event () - 单元事件。这可以作为发送 Char 关闭的结果,以显示在屏幕上的某个位置:

viewChar :: Char -> Time -> Event ()

同样,可以使用类似的技术处理其他输出。


...那是什么 - 它不是外延的? 因为viewChar 是...什么 - 不纯? 如果是这样,那意味着lbprbp 也是不纯的——你确定吗?

好吧...让我们有一个类型来接收一系列鼠标按钮按下或其他事件:

type Intake a = [a]

lpb, rbp :: Intake (Event (Event ())

那个更好吗?好的!嗯,有点—— 如果鼠标被拔掉会发生什么?那 可以将程序的一部分放入等待输入的旋转中(并且使用 [] 将永久结束该系列 - 不再按下按钮!)。

我们需要更改Intake:

data Intake a = None (Intake a) | Next a (Intake a) 

现在拔掉鼠标会导致None … 出现,程序可以检测到并做出相应的反应,例如产生它的操作系统线程, 暂停自身等。

那么,输出呢?好吧,输出设备通常也可以拔掉。来自Intake的提示:

data Outlet a = Wait (Outlet a) | Went (… (Outlet a) …) 

类似于拔掉输入设备——遇到Wait …,程序可以暂停传输。

那么Went 的类型应该是什么?好吧,Outlet 逐渐接受值以允许 Wait … 在需要时出现 - 每个值的接受应该向我们展示 Output 的其余部分。因此:

data Outlet a = Wait (Outlet a) | Went (a -> Outlet a)

完全做到这一点:

data Intake a = None (Intake a) | Next a (Intake a)

lbp, rbp :: Intake (Event (Event ())


data Outlet a = Wait (Outlet a) | Went (a -> Outlet a)

viewChar :: Outlet Char

那么这一切都有效吗?如果您不确定,请参阅 Magnus Carlsson 和 Thomas Hallgren 的 Fudgets - Purely Functional Processes with applications to Graphical User Interfaces 的第 20.4.2 节(第 86 页,共 263 页) - 如果 IntakeOutlet 看起来很可疑,那么在纸...


【讨论】:

以上是关于功能响应式编程语言规范的主要内容,如果未能解决你的问题,请参考以下文章

响应式编程前生今世-从规范到实例

spring5中的响应式编程框架webflux,到底是什么东西啊?

学习响应式编程 Reactor - reactor 基础

Kotlin结合Spring WebFlux实现响应式编程

我对响应式编程中Mono和Flux的理解

响应式编程机制总结