代码之家  ›  专栏  ›  技术社区  ›  JtR

什么是(函数式)响应式编程?

  •  1147
  • JtR  · 技术社区  · 17 年前

    reactive programming functional reactive programming

    1. 函数式反应式编程(FRP)在实践中意味着什么?

    18 回复  |  直到 9 年前
        1
  •  930
  •   Conal    8 年前

    如果你想了解FRP,你可以从旧的开始 Fran tutorial 1998年,它有动画插图。对于论文,请从以下内容开始 Functional Reactive Animation 然后跟进我主页上的出版物链接和 FRP Haskell wiki .

    (没有规范的代码是没有问题的答案,因此“甚至没有错”。) 因此,我不会像Thomas K在另一个答案(图、节点、边、触发、执行等)中那样用表示/实现的术语来描述FRP。 .

    完整的历史(过去、现在、未来)没有一流的代表性。 由于命令式范式在时间上是离散的,因此可以(间接地)捕获值。 相比之下,FRP捕捉到了这些不断变化的价值观 持续地

    细粒的 , 连续的 . (我说的是意义,而不是实现。实现可能涉及也可能不涉及并发或并行。) 语义确定性对于推理非常重要,无论是严谨的还是非正式的。 虽然并发性给命令式编程增加了巨大的复杂性(由于非确定性交织),但在FRP中却毫不费力。

    那么,什么是FRP? 从这些想法开始:

    如果你坚持这些原则,我希望你或多或少会得到一些符合FRP精神的东西。

        2
  •  737
  •   Laurence Gonsalves    14 年前

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

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

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

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

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

    minX 将始终比鼠标指针的x坐标小16。使用反应式感知库,您可以这样说:

    rectangle(minX, minY, maxX, maxY)
    

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

    这是一个相当不错的 paper on functional reactive programming .

        3
  •  144
  •   Bob Bob    17 年前

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

        4
  •  131
  •   Gilles 'SO- stop being evil'    13 年前

    对我来说,这是关于符号的两种不同含义 = :

    1. x = sin(t) 意思是 x sin(t) x + y sin(t) + y 函数式反应式编程在这方面就像数学:如果你写 t 在使用时。
    2. 是一项任务:这意味着 X 价值
        5
  •  69
  •   Thomas Kammeyer    9 年前

    好的,从背景知识和阅读你指向的维基百科页面来看,反应式编程似乎有点像数据流计算,但有特定的外部“刺激”触发一组节点启动并执行计算。

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

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

    节点可以被视为并行启动,但它们通常是串行执行的,或者并行性有限(例如,可能有几个线程在执行它们)。一个著名的例子是 Manchester Dataflow Machine ,其(IIRC)使用标记数据架构通过一个或多个执行单元来调度图中节点的执行。数据流计算非常适合异步触发计算以产生级联计算的情况,而不是试图由一个或多个时钟来控制执行。

    “非反应性”编程将是对执行流和与外部输入的关系有非常不同的看法的编程。这可能有点主观,因为人们可能会想说任何对外部输入有反应的话。但从精神上看,一个以固定间隔轮询事件队列并将发现的任何事件分派给函数(或线程)的程序反应性较差(因为它只以固定间隔处理用户输入)。同样,这也是事情的精神所在:人们可以想象将一个具有快速轮询间隔的轮询实现以非常低的级别放入系统中,并在其之上以被动的方式进行编程。

        6
  •  65
  •   jhegedus    8 年前

    this 关于FRP的启发性写作,终于让我明白了FRP的真正含义。

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

    函数式反应式编程的本质是什么?

    当然不会错。这是语义的观点。但在 遵循纯粹的句法标准:

    例如,以计数器为例:您有两个按钮

    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程序是一组方程式: enter image description here

    j

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

    x_i

    j j+1 .

    this

    编辑:

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

    x_i x_i 时间变化 j 然后不是所有其他 x_i' x_i' x_i .

    x_i -确实发生变化的s可以逐步更新。例如,让我们考虑一个映射操作 f=g.map(_+1) f g List Ints 在这里 f x_i(t_j) x_j(t_j) 。现在,如果我在前面添加一个元素 map reflex-frp incremental computing.

    x_i x_i f_i

        8
  •  28
  •   tldr    9 年前


    i) 它从代码中抽象出状态,例如,如果你想让事件处理程序只对每个'n'个事件触发,或者在第一个'n'事件后停止触发,或者只在第一个'an'事件之后开始触发,你可以只使用HoFs(分别为filter、takeUntil、skip),而不是设置、更新和检查计数器。
    ii)它改进了代码局部性-如果你有5个不同的事件处理程序更改组件的状态,你可以合并它们的可观察对象,并在合并的可观察到的对象上定义一个事件处理程序,从而有效地将5个事件处理程序组合成1个。这使得很容易推断整个系统中的哪些事件会影响组件,因为它们都存在于一个处理程序中。

    • Observable是Iterable的对偶。

    Iterable是一个延迟消费的序列——每当迭代器想要使用它时,它都会拉取每个项目,因此枚举是由消费者驱动的。

        9
  •  18
  •   Dan Ross    14 年前

    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()
    


        10
  •  14
  •   David Lemon amitgoyal    8 年前

    保罗·胡达克的书, The Haskell School of Expression ,不仅是对Haskell的一个很好的介绍,而且在FRP上也花了相当多的时间。如果你是FRP的初学者,我强烈建议你了解FRP的工作原理。

    这本书(2011年出版,2014年更新)似乎也有新的重写, The Haskell School of Music .

        11
  •  10
  •   Yuning    10 年前

    X 十、 X : T 十、 T 是时间的类型,无论是自然数、整数还是连续体。现在,当我们写作 y X +1在编程语言中,我们实际上是指方程式 y ( X (

        12
  •  9
  •   emperorz    12 年前

    如前所述,其行为类似于电子表格。通常基于事件驱动框架。

    根据我对参与者分布式流网络的经验,它很容易成为节点网络状态一致性的一般问题的牺牲品,即你最终会在奇怪的循环中出现很多振荡和陷阱。

        14
  •  7
  •   Community Mohan Dere    6 年前

    This article 安德烈·斯塔尔茨的这篇文章是我迄今为止看到的最好、最清晰的解释。

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

    最重要的是,你会得到一个惊人的函数工具箱,可以组合、创建和过滤任何这些流。

    Click event stream diagram

        15
  •  5
  •   Dmitri Zaitsev dpedro    9 年前

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

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

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

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

    行为是事件流+内存中的某个值。

    http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

        16
  •  2
  •   pdorgambide    9 年前

    Cyclejs - Reactive Programming ,它使用简单直观的样本。

    意味着它完全负责 通过对外部事件做出反应来管理自己的状态。

    ,

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

        17
  •  0
  •   Sentinel    9 年前

    (摘自MyObservableSetOfMouseMovements中的m 选择新点(m.X,m.Y))。

        18
  •  0
  •   Krishna Ganeriwal    8 年前

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