我已经阅读了维基百科上的文章 反应的编程.我也读的小条 功能的反应编程.该描述是相当抽象的。

  1. 什么是功能性的反应程(玻璃钢)在实践中意味着?
  2. 是什么反应程(而非反应编程?) 由?

我的背景是,必须/OO语言,以解释涉及到这种模式将不胜感激。

有帮助吗?

解决方案

如果你想感受一下玻璃钢,你可以开始与老 弗兰教程 1998年,其具有的动画插图。对文件,开始 功能的反应的动画 然后按照立联系的出版物的链接在我的主页和 玻璃钢 链接 Haskell wiki.

就个人而言,我喜欢想到什么的玻璃钢 装置 之前解决它如何可能实现的。(码没有说明书是一个答案没有一个问题,因此"不甚至错误的"。) 所以我不描述了玻璃钢代表/执行条款,因为托马斯*K并在另一个答复(图表,节点、边发射、执行、等等)。有许多可能实现的风格,但是没有实施说什么的玻璃钢 .

我做的共鸣的劳伦斯*克的简单描述,玻璃钢约"的数据类型,代表着价值随时间'".常规言编写了这些动态的价值观只是间接的,通过国和突变。完整的历史(过去、现在和将来)没有第一类代表性。此外,只有 分散地不断演变的 值可以(间接)捕获的,因为必要的范式是暂时离散。相反,玻璃钢抓住这些不断变化的价值观 直接 和没有困难 连续 不断变化的价值观。

玻璃钢也是不寻常的在于,它并没有运行中发生冲突的理论&务实的老鼠窝里是瘟疫的必要并发。在语义上,玻璃钢的并发 细粒, 确定, , 连续的.(我说的意思,不执行。执行可能涉及也可能不涉及并发或并行。) 语义determinacy是非常重要的推理,这两种严格的和非正式的。同时并发,增加了巨大的复杂性,必须编程(由于不确定的交错),这是毫不费力的在玻璃纤维强化塑料.

那么,什么是玻璃?你可能已经发明了它自己。开始与这些想法:

  • 动态/不断变化的数值(即,值"随时间演变")是一流的价值在他们自己。你可以界定他们和将它们结合起来,通过他们进入和输出功能。我称这些事情"行为".

  • 行为都建立了一些原语,像经常(静态)的行为和时间(就像一个钟),然后顺序和并行结合。 n 行为是合并通过应用一个n进功能(在静值),"指明",即,持续时间。

  • 考虑到离散的现象,有另一种类型(家庭)的"事件",每一个都有一个流(有限或无限)的情况。每次出现有关联的时间和价值。

  • 来作曲的词汇,其中所有行为和活动可以建立,起到与一些例子。保持解构成碎片,更一般/简单。

  • 所以,你知道,你在坚实的基础,给整个模型的一个组成基础,使用的技术denotational语义,这就意味着(a)每一类都有相应简单和精确的数学类型的"含义",以及(b)每个原始人和操作者具有一个简单的和准确含义作为一种功能的含义的成分。永远,永远 混合实施考虑到你的探索进程。如果这种描述是胡言乱语你,咨询(a) Denotational设计类型态射, (b) 推拉功能的反应编程 (忽略的执行情况位)、和(c) Denotational义 Haskell维基教科书页.请注意,denotational义有两个部分,从它的两个创始人克里斯托弗*斯特雷奇和丹娜*斯科特:本更容易和更加有用斯特雷奇的部分和难和不用(用软件的设计)*斯科特的部分。

如果你坚持这些原则,我希望你得到的东西或多或少精神的玻璃纤维强化塑料.

我在哪里得到这些原则是什么?在软件的设计,我总是问同样的问题:"是什么意思?".Denotational义给了我一个精确的框架,这个问题,一个适合我的审美观(不同于行动或不言自明的语义,两者都让我不满意的).所以我问自己是什么行为?我很快意识到,在时间上的离散性质的必要的计算是一个住宿的特定风格的 , 而不是一个自然的描述的行为本身。最简单的精确描述的行为我能想到的只是"功能(续)时间",因此这就是我的模型。兴高采烈,这种模式处理的持续、确定并发与缓解和宽限期。

这是一个很大的挑战,以实现这个模型的正确和有效的,但这是另一个故事。

其他提示

在纯函数式编程中,没有副作用。对于许多类型的软件(例如,任何具有用户交互的东西),在某种程度上都需要副作用。

在保留功能风格的同时获得类似行为的副作用的一种方法是使用功能性反应式编程。这是函数式编程和反应式编程的组合。 (你链接的维基百科文章是关于后者的。)

反应式编程背后的基本思想是,某些数据类型表示值<!>“随着时间的推移<!>”。涉及这些随时间变化的值的计算本身会有随时间变化的值。

例如,您可以将鼠标坐标表示为一对整数时间值。假设我们有类似的东西(这是伪代码):

x = <mouse-x>;
y = <mouse-y>;

在任何时候,x和y都会有鼠标的坐标。与非反应式编程不同,我们只需要进行一次此分配,x和y变量将保持<!>“最新<!>”;自动。这就是为什么反应式编程和函数式编程能够很好地协同工作的原因:反应式编程消除了变异变量的需要,同时仍然允许你通过变量突变完成许多工作。

如果我们基于此进行一些计算,结果值也将是随时间变化的值。例如:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

在此示例中,minX将始终比鼠标指针的x坐标小16。使用反应感知库,您可以说:

rectangle(minX, minY, maxX, maxY)

鼠标指针周围会绘制一个32x32的盒子,无论它移动到哪里都会跟踪它。

这是一个非常好的有关功能的论文反应式节目

想象一下你的程序是一个电子表格并且所有变量都是单元格的简单方法是获得关于它的第一个直觉。如果电子表格中的任何单元格发生更改,则引用该单元格的任何单元格也会发生更改。它和FRP一样。现在想象一些单元格自行改变(或者更确切地说,取自外部世界):在GUI情况下,鼠标的位置将是一个很好的例子。

这必然会错过很多。当你实际使用FRP系统时,这个比喻会很快崩溃。例如,通常也尝试对离散事件进行建模(例如,点击鼠标)。我只是把它放在这里,让你知道它是什么样的。

对我而言,这是符号=的两种不同含义:

  1. 在数学中x = sin(t)表示x sin(t) 不同名称。所以写x + ysin(t) + y是一回事。功能反应式编程在这方面就像数学一样:如果你写t,它的计算与<=>的值在使用时无关。
  2. 在类C编程语言(命令式语言)中,<=>是一项任务:它意味着<=>存储在分配时所采用的 <=>。

好的,从背景知识和阅读您指向的维基百科页面看来,似乎反应性编程类似于数据流计算,但具有特定的外部<!>刺激<!>;触发一组节点来触发并执行计算。

这非常适合UI设计,例如,触摸用户界面控件(例如,音乐播放应用程序上的音量控制)可能需要更新各种显示项目和音频输出的实际音量。当你修改音量(一个滑块,比方说)时,它对应于修改与有向图中节点相关的值。

具有来自<!>“音量值<!>的边缘的各种节点节点将自动被触发,任何必要的计算和更新自然会在应用程序中波及。应用程序<!>“响应<!>”;对用户的刺激。功能性反应式编程只是在函数式语言中实现,或者通常在函数式编程范式中实现。

有关<!>“数据流计算<!>”的更多信息,请在维基百科上搜索这两个单词或使用您最喜欢的搜索引擎。一般的想法是这样的:程序是节点的有向图,每个节点执行一些简单的计算。这些节点通过图形链接相互连接,图形链接将某些节点的输出提供给其他节点的输入。

当节点触发或执行其计算时,连接到其输出的节点具有相应的输入<!>“触发<!>”;或<!>“;标记为<!>”;触发/标记/可用的所有输入的任何节点都会自动触发。该图可能是隐式或显式的,具体取决于如何实现反应式编程。

节点可以被视为并行触发,但通常它们是串行执行的或者具有有限的并行性(例如,可能有几个线程执行它们)。一个着名的例子是曼彻斯特数据流机器,它(IIRC)使用标记数据架构来通过一个或多个执行单元安排图中节点的执行。数据流计算非常适合于这样的情况,其中异步触发计算产生级联计算比尝试执行由时钟(或时钟)控制更好。

反应式编程导入此<!>“执行级联<!>”;想法并且似乎以类似数据流的方式考虑该程序,但条件是某些节点连接到<!>“外部世界<!>”;当这些类似感觉的节点改变时,触发执行的级联。然后程序执行看起来像复杂的反射弧。该程序在刺激之间可能基本上是无柄的,也可能不是刺激之间的基本无柄状态。

<!>

QUOT; <!>非反应性QUOT;编程将使用与执行流程和与外部输入的关系的非常不同的视图进行编程。它可能有点主观,因为人们很可能会说出任何响应外部输入的内容<!>“反应<!>”;给他们。但是看一下这个东西的精神,一个以固定间隔轮询事件队列并调度发现给函数(或线程)的任何事件的程序反应性较小(因为它只能以固定的间隔参与用户输入)。再一次,这就是这里的精神:人们可以想象将具有快速轮询间隔的轮询实现放入一个非常低级别的系统中,并以一种被动方式编程。

在阅读了很多关于FRP的网页后,我终于遇到了这个关于FRP的启发性写作,它最终让我明白了FRP究竟是什么。

我引用了Heinrich Apfelmus(反应性香蕉的作者)。

  

功能反应式编程的本质是什么?

     

一个常见的答案是<!>#8220; FRP就是在描述一个系统   时变函数的术语而不是可变状态<!>#8221;和那   肯定没错。这是语义观点。但在   我认为,更深刻,更令人满意的答案是由   遵循纯粹的句法标准:

     

功能性反应式编程的本质是在声明时完全指定值的动态行为。

     

例如,以计数器为例:您有两个按钮   标记为<!>#8220; Up <!>#8221;和<!>#8220;下来<!>#8221;可用于递增或递减   柜台。当然,您首先要指定一个初始值   然后在按下按钮时更改它;像这样的东西:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)
     

关键是在声明时,只有初始值   指定柜台;计数器的动态行为是   隐含在程序文本的其余部分中。相比之下,功能性   反应式编程指定了当时的整个动态行为   声明,像这样:

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)
     

每当你想要理解计数器的动态时,你就只有   看看它的定义。可能发生的一切都会发生   出现在右侧。这与之形成鲜明对比   后续声明可以改变的必要方法   先前声明的值的动态行为。

所以,在我的理解中,FRP程序是一组方程式:

j是离散的:1,2,3,4 ...

f取决于t,因此这包含了模拟外部刺激的可能性

程序的所有状态都封装在变量x_i

FRP库负责处理进度,换句话说,将j+1带到x_i'

我在视频中更详细地解释了这些等式。

修改

在最初答案之后大约2年,最近我得出的结论是,FRP实施还有另一个重要方面。他们需要(并且通常会)解决一个重要的实际问题:缓存失效

f=g.map(_+1) - s的等式描述了依赖图。当某些g在时间List时发生更改时,并非Ints所有其他x_i(t_j)值都需要更新,因此不需要重新计算所有依赖项,因为某些x_j(t_j)可能独立于< =>。

此外,map - s可以逐步更新。例如,让我们考虑Scala中的地图操作f_i,其中<=>和<=>是<=> <=>。这里<=>对应<=>而<=>对应<=>。现在,如果我将元素添加到<=>,那么对<=>中的所有元素执行<=>操作将是浪费的。一些FRP实现(例如 reflex-frp )旨在解决这个问题。此问题也称为增量计算。

换句话说,FRP中的行为(<=> - s)可以被认为是缓存计算。 FRP引擎的任务是有效地使这些cac无效和重新计算he-s(<=> - s)如果某些<=> - s确实发生了变化。

Conal Elliott论文 简单有效的功能反应 直接PDF ,233 <!> nbsp; KB)是一个相当不错的介绍。相应的库也可以使用。

该论文现已被另一篇论文取代, 推拉式功能反应式编程 直接PDF , 286 NBSP <!>; KB)

免责声明:我的答复是在上下文中rx.js -a'反应程'库Javascript。

在编程功能,而不是通过迭代,每个项目的收集、应用高秩序的职能(HoFs)收集本身。这样的想法背后的玻璃钢,而不是处理每个单独的事件,创建一个流的活动(执行与可观察到的*)和适用HoFs来代替。这样你就可以形象化的系统数据管道连接出版商订阅者。

主要的优点使用可观察到的是:
i)摘要离开状态从你的代码,例如,如果你想要事件处理被炒鱿鱼只能为每一个'n'th事件,或停止射击之后的第一个'n'的活动,或开始射击之后,才第一个'n'事件,你就可以使用的HoFs(滤波器,takeUntil,跳过分别),而不是设置、更新和检查计数器。
ii)提高码的地方-如果你有5种不同的事件处理程序的改变态的一个组件,可以合并他们的观测值和定义的单个事件处理程序的合并可观察的相反,有效地结合5的事件处理程序的成1.这使得它很容易理解什么事件在整个系统的可能影响一个组件,因为它是所有存在一个单一的处理程序。

  • 可观察到的是双重的一个迭代.

一个迭代的是一个懒洋洋地消耗序列的每个项目是通过拉的迭代,每当它希望使用它,因此将列举驱动的消费者。

可观察到的是一个懒洋洋地生产序列的每个项目都推到观察员,只要其加入的序列,因此列举驱动的生产商。

老兄,这是一个非常棒的主意!为什么我在1998年没有发现这个?无论如何,这是我对 Fran 教程的解释。建议是最受欢迎的,我正在考虑基于此开始一个游戏引擎。

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

简而言之:如果每个组件都可以像数字一样处理,整个系统可以像数学公式一样对待,对吗?

Paul Hudak的书, Haskell表达学校,不是只是对Haskell的一个很好的介绍,但它也花了相当多的时间在FRP上。如果您是FRP的初学者,我强烈建议您了解FRP的工作原理。

本书的新内容(2011年发布,2014年更新)也是如此,哈斯克尔音乐学院

根据之前的答案,似乎在数学上,我们只是按更高的顺序思考。我们不考虑具有 x 类型的值 x ,而是考虑函数 x T <!># 8594; X ,其中 T 是时间的类型,无论是自然数,整数还是连续体。现在,当我们在编程语言中编写 y := x + 1时,我们实际上意味着等式 y t )= x t )+ 1。

就像电子表格一样。通常基于事件驱动的框架。

与所有<!>“范例<!>”一样,它的新颖性值得商榷。

根据我对演员的分布式流网络的经验,它很容易成为整个节点网络中状态一致性的一般问题的牺牲品,即最终会在奇怪的循环中产生大量振荡和陷阱。

这很难避免,因为某些语义意味着参考循环或广播,并且当演员网络在一些不可预测的状态上收敛(或不收敛)时可能会非常混乱。

同样,尽管有明确定义的边缘,但可能无法达到某些状态,因为全球状态偏离了解决方案。 2 + 2可能会或可能不会变为4取决于2的2变为2,以及他们是否保持这种状态。电子表格具有同步时钟和循环检测。分布式演员通常不会。

一切都很有趣:)。

我在关于FRP的Clojure subreddit上发现了这个不错的视频。即使你不了解Clojure,也很容易理解。

以下是视频: http://www.youtube.com/watch?v=nket0K1RXU4

以下是视频在下半部分引用的来源: https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs

来自Andre Staltz的本文是迄今为止我见过的最好,最清晰的解释。

文章中的一些引用:

  

反应式编程是使用异步数据流进行编程。

     

最重要的是,您将获得一个惊人的功能工具箱来组合,创建和过滤任何这些流。

这是作为本文一部分的精彩图表的一个例子:

它是关于随时间推移的数学数据转换(或忽略时间)。

在代码中,这意味着功能纯度和声明性编程。

状态错误在标准命令式范例中是一个巨大的问题。各种代码位可以在不同的<!>“时间<!>”改变某些共享状态。在程序执行中。这很难处理。

在FRP中,您描述(如在声明性编程中)数据如何从一种状态转换为另一种状态以及触发它的原因。这允许您忽略时间,因为您的函数只是对其输入作出反应并使用它们的当前值来创建新的输入。这意味着状态包含在转换节点的图形(或树)中,并且在功能上是纯粹的。

这大大降低了复杂性和调试时间。

想想数学中A = B + C和程序中A = B + C之间的区别。 在数学中,你描述的是一种永远不会改变的关系。在一个程序中,它表示<!>“;现在<!>”; A是B + C.但是下一个命令可能是B ++,在这种情况下A不等于B + C.在数学或声明性编程中,无论你提出什么时间点,A总是等于B + C.

因此,通过消除共享状态的复杂性和随时间变化的值。你的程序更容易推理。

EventStream是一个EventStream +一些转换函数。

行为是一个EventStream +内存中的一些值。

当事件触发时,通过运行转换函数更新值。它产生的值存储在行为记忆中。

可以组织行为以产生对N个其他行为进行转换的新行为。当输入事件(行为)触发时,此组合值将重新计算。

<!>“由于观察者是无状态的,我们经常需要其中几个来模拟状态机,就像在拖动示例中一样。我们必须将所有相关观察者都可以访问的状态保存在上面的变量路径中。<!> quot;

引用自 - 弃用观察者模式 http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010。 PDF

关于反应式编程的简短明了的解释出现在 Cyclejs - Reactive Programming 上,它使用了简单直观的样本。

  

[module / Component / object] 是被动的意味着它是完全负责任的   通过对外部事件做出反应来管理自己的状态。

     

这种方法有什么好处?它是控制反转,   主要是因为[module / Component / object]对自己负责,使用私有方法改进对公共方法的封装。

这是一个很好的创业点,而不是知识的完整来源。从那里你可以跳到更复杂和更深的论文。

查看Rx,.NET的Reactive Extensions。他们指出,使用IEnumerable,你基本上是从流中“拉”出来的。对IQueryable / IEnumerable的Linq查询是设置操作,它们将结果“吮吸”出来。但是通过IObservable上的相同运算符,您可以编写“反应”的Linq查询。

例如,您可以编写类似的Linq查询 (来自MyObservableSetOfMouseMovements中的m 其中m.X <!> <100>并且m.Y <!> <100 选择新点(m.X,m.Y))。

并且使用Rx扩展,就是这样:你有UI代码可以对输入的鼠标移动流作出反应,并在你进入100,100盒时绘制...

FRP是函数式编程(基于一切都是函数的编程范式)和反应式编程范式的组合(建立在一切都是流(观察者和可观察哲学)的思想的基础上)。它应该是世界上最好的。

请查看Andre Staltz关于反应式编程的帖子。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top