O que é programação reativa (funcional)?

1148

Eu li o artigo da Wikipedia sobre programação reativa . Também li o pequeno artigo sobre programação reativa funcional . As descrições são bastante abstratas.

  1. O que significa programação reativa funcional (FRP) na prática?
  2. Em que consiste a programação reativa (em oposição à programação não reativa?)?

Minha formação é em linguagens imperativas / OO, então uma explicação relacionada a esse paradigma seria apreciada.

JtR
fonte
159
aqui está um cara com uma imaginação ativa e boas habilidades de contar histórias. paulstovell.com/reactive-programming
melaos
39
Alguém realmente precisa escrever uma "Programação Reativa Funcional para Leigos" para todos nós autodidata aqui. Todo recurso que encontrei, até Elm, parece presumir que você tenha obtido um mestrado em CS nos últimos cinco anos. Os conhecedores da FRP parecem ter perdido completamente a capacidade de ver o assunto do ponto de vista ingênuo, algo crítico para o ensino, o treinamento e a evangelização.
TechZen
26
Outra excelente introdução FRP: A introdução de Reactive Programação você está perdendo pelo meu colega André
Jonik
5
Um dos melhores que já vi, Exemplo base: gist.github.com/staltz/868e7e9bc2a7b8c1f754
Razmig
2
Acho a analogia da planilha muito útil como uma primeira impressão aproximada (consulte a resposta de Bob: stackoverflow.com/a/1033066/1593924 ). Uma célula da planilha reage a alterações em outras células (puxa), mas não alcança e muda outras (não pressiona). O resultado final é que você pode alterar uma célula e mais um zilhão de outras 'atualizar' suas próprias exibições.
Jon Coombs

Respostas:

931

Se quiser ter uma idéia do FRP, comece com o antigo tutorial de Fran de 1998, que tem ilustrações animadas. Para artigos, comece com Animação Reativa Funcional e siga os links no link de publicações na minha home page e o link FRP no wiki da Haskell .

Pessoalmente, gosto de pensar no que significa FRP antes de abordar como ele pode ser implementado. (Código sem especificação é uma resposta sem uma pergunta e, portanto, "nem errado".) Portanto, não descrevo o FRP em termos de representação / implementação, como Thomas K em outra resposta (gráficos, nós, arestas, disparo, execução, etc). Existem muitos estilos de implementação possíveis, mas nenhuma implementação diz o que é FRP .

Eu ressoo com a descrição simples de Laurence G de que o FRP é sobre "tipos de dados que representam um valor 'ao longo do tempo'". A programação imperativa convencional captura esses valores dinâmicos apenas indiretamente, através de estado e mutações. A história completa (passado, presente, futuro) não tem representação de primeira classe. Além disso, apenas valores em evolução discreta podem ser (indiretamente) capturados, uma vez que o paradigma imperativo é temporalmente discreto. Por outro lado, o FRP captura esses valores em evolução diretamente e não tem dificuldade em evoluir continuamente .

O FRP também é incomum, pois é concorrente sem colidir com o ninho de ratos teórico e pragmático que assola a concorrência imperativa. Semanticamente, a simultaneidade do FRP é refinada , determinada e contínua . (Estou falando de significado, não de implementação. Uma implementação pode ou não envolver simultaneidade ou paralelismo.) A determinação semântica é muito importante para o raciocínio, rigoroso e informal. Embora a concorrência acrescente uma complexidade enorme à programação imperativa (devido à intercalação não determinística), ela é fácil no FRP.

Então, o que é FRP? Você poderia ter inventado você mesmo. Comece com estas idéias:

  • Valores dinâmicos / em evolução (ou seja, valores "ao longo do tempo") são valores de primeira classe em si mesmos. Você pode defini-los e combiná-los, passá-los para dentro e fora das funções. Eu chamei essas coisas de "comportamentos".

  • Os comportamentos são criados a partir de algumas primitivas, como comportamentos constantes (estáticos) e tempo (como um relógio), e depois com combinações sequenciais e paralelas. Os comportamentos n são combinados aplicando uma função n-ária (em valores estáticos), "ponto a ponto", ou seja, continuamente ao longo do tempo.

  • Para explicar fenômenos discretos, tenha outro tipo (família) de "eventos", cada um dos quais com um fluxo (finito ou infinito) de ocorrências. Cada ocorrência tem um tempo e valor associados.

  • Para criar o vocabulário de composição a partir do qual todos os comportamentos e eventos podem ser construídos, brinque com alguns exemplos. Continue desconstruindo em peças mais gerais / simples.

  • Para que você saiba que está em uma base sólida, forneça a todo o modelo uma base de composição, usando a técnica da semântica denotacional, o que significa apenas que (a) cada tipo tem um tipo matemático simples e preciso correspondente de "significados" e ( b) cada primitivo e operador tem um significado simples e preciso em função dos significados dos constituintes. Nunca, nunca misture considerações de implementação em seu processo de exploração. Se essa descrição lhe parecer absurda, consulte (a) design denotacional com morfismos de classe de tipo , (b) programação reativa funcional push-pull (ignorando os bits de implementação) e (c) a página de wikilivros de Denotational Semantics Haskell. Observe que a semântica denotacional tem duas partes, de seus dois fundadores, Christopher Strachey e Dana Scott: a parte Strachey, mais fácil e útil, e a parte Scott, mais difícil e menos útil (para o design de software).

Se você seguir esses princípios, espero que consiga algo mais ou menos no espírito do FRP.

Onde consegui esses princípios? No design de software, eu sempre faço a mesma pergunta: "o que isso significa?". A semântica denotacional me deu uma estrutura precisa para essa questão e que se encaixa na minha estética (diferente da semântica operacional ou axiomática, ambas as quais me deixam insatisfeito). Então eu me perguntei o que é comportamento? Logo percebi que a natureza temporalmente discreta da computação imperativa é uma acomodação a um estilo particular de máquina , em vez de uma descrição natural do próprio comportamento. A descrição mais simples e precisa do comportamento em que consigo pensar é simplesmente "função do tempo (contínuo)", então esse é o meu modelo. Deliciosamente, esse modelo lida com simultaneidade determinística contínua com facilidade e graça.

Tem sido um grande desafio implementar esse modelo de maneira correta e eficiente, mas isso é outra história.

Conal
fonte
78
Eu tenho conhecimento da programação reativa funcional. Parece relacionado à minha própria pesquisa (em gráficos estatísticos interativos) e tenho certeza de que muitas das idéias seriam úteis para o meu trabalho. No entanto, acho muito difícil superar a linguagem - devo realmente aprender sobre "semântica denotacional" e "morfismos de classe de tipo" para entender o que está acontecendo? Uma introdução do público em geral ao tópico seria muito útil.
23411 hadley
212
@Conal: você sabe claramente do que está falando, mas sua linguagem presume que eu tenho um doutorado em matemática computacional, o que eu não sei. Tenho experiência em engenharia de sistemas e mais de 20 anos de experiência com computadores e linguagens de programação, mas sinto que sua resposta me deixa perplexo. Eu desafio você a repassar sua resposta em Inglês ;-)
mindplay.dk
50
@ minplay.dk: Suas observações não me dão muito o que falar em particular sobre o que você não entende, e não estou disposto a fazer palpites sobre o subconjunto específico de inglês que você está procurando. No entanto, convido você a dizer especificamente quais aspectos da minha explicação acima você está discutindo, para que eu e outros possamos ajudá-lo. Por exemplo, há palavras específicas que você deseja definir ou conceitos para os quais deseja adicionar referências? Eu realmente gosto de melhorar a clareza e a acessibilidade da minha escrita - sem emburrecer.
Conal
27
"Determinação" / "determinado" significa que há um valor correto único e bem definido. Por outro lado, quase todas as formas de simultaneidade imperativa podem fornecer respostas diferentes, dependendo de um agendador ou se você está procurando ou não, e elas podem até causar um impasse. "Semântica" (e mais especificamente "denotacional") refere-se ao valor ("denotação") de uma expressão ou representação, em contraste com "operacional" (como a resposta é calculada ou quanto espaço e / ou tempo são consumidos pelo que tipo de máquina).
Conal
18
Eu concordo com @ mindplay.dk, embora eu não possa me gabar de estar no campo por muito tempo. Embora parecesse que você sabia do que estava falando, isso não me deu uma compreensão rápida, breve e simples do que é isso, pois sou mimada o suficiente para esperar pelo SO. Essa resposta me levou a uma tonelada de novas perguntas sem realmente responder à minha primeira. Espero que compartilhar a experiência de ainda ser relativamente ignorante no campo possa lhe dar uma ideia de quão simples e breve você realmente precisa ser. Eu venho de um fundo semelhante ao OP, aliás.
Aske B.
739

Na programação funcional pura, não há efeitos colaterais. Para muitos tipos de software (por exemplo, qualquer coisa com interação do usuário), os efeitos colaterais são necessários em algum nível.

Uma maneira de obter um efeito colateral como um efeito coletivo e ainda manter um estilo funcional é usar a programação reativa funcional. Esta é a combinação de programação funcional e programação reativa. (O artigo da Wikipedia ao qual você vinculou é sobre o último.)

A idéia básica por trás da programação reativa é que existem certos tipos de dados que representam um valor "ao longo do tempo". Os cálculos que envolvem esses valores de mudança ao longo do tempo terão eles mesmos valores que mudam ao longo do tempo.

Por exemplo, você pode representar as coordenadas do mouse como um par de valores de número inteiro ao longo do tempo. Digamos que tivemos algo como (isto é pseudo-código):

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

A qualquer momento, x e y teriam as coordenadas do mouse. Diferentemente da programação não reativa, precisamos fazer essa atribuição apenas uma vez, e as variáveis ​​x e y permanecerão "atualizadas" automaticamente. É por isso que a programação reativa e a programação funcional funcionam tão bem juntas: a programação reativa elimina a necessidade de alterar variáveis ​​enquanto ainda permite fazer muito do que você poderia realizar com mutações variáveis.

Se fizermos alguns cálculos com base nisso, os valores resultantes também serão aqueles que mudam ao longo do tempo. Por exemplo:

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

Neste exemplo, minXsempre será 16 menor que a coordenada x do ponteiro do mouse. Com bibliotecas reativas, você poderia dizer algo como:

rectangle(minX, minY, maxX, maxY)

E uma caixa de 32 x 32 será desenhada ao redor do ponteiro do mouse e o rastreará onde quer que ele se mova.

Aqui está um artigo muito bom sobre programação reativa funcional .

Laurence Gonsalves
fonte
25
Então a programação reativa é uma forma de programação declarativa?
troelskn
31
> Então a programação reativa é uma forma de programação declarativa? A programação reativa funcional é uma forma de programação funcional, que é uma forma de programação declarativa.
Conal
7
@ user712092 Na verdade, não. Por exemplo, se eu chamar sqrt(x)C com sua macro, isso apenas calcula sqrt(mouse_x())e me devolve um duplo. Em um verdadeiro sistema reativo funcional, sqrt(x)retornaria um novo "dobro ao longo do tempo". Se você tentasse simular um sistema de FR, #defineteria que jurar variáveis ​​em favor de macros. Os sistemas FR também normalmente recalculam as coisas quando precisam ser recalculadas, enquanto o uso de macros significa que você está constantemente reavaliando tudo, até as subexpressões.
Laurence Gonsalves
4
"Para muitos tipos de software (por exemplo, qualquer coisa com interação do usuário), os efeitos colaterais são necessários em algum nível". E talvez apenas no nível de implementação. Há muitos efeitos colaterais na implementação de programação funcional pura e preguiçosa, e um dos sucessos do paradigma é manter muitos desses efeitos fora do modelo de programação. Minhas próprias incursões nas interfaces funcionais do usuário sugerem que elas também podem ser programadas inteiramente sem efeitos colaterais.
Conal
4
@tieTYT x nunca é reatribuído / alterado. O valor de x é a sequência de valores ao longo do tempo. Outra maneira de analisar é que, em vez de x ter um valor "normal", como um número, o valor de x é (conceitualmente) uma função que leva tempo como parâmetro. (Isso é um pouco de uma simplificação Você não pode criar valores de tempo que lhe permitem prever o futuro de coisas como a posição do mouse..)
Laurence Gonsalves
144

Uma maneira fácil de alcançar uma primeira intuição sobre como é imaginar o seu programa é uma planilha e todas as suas variáveis ​​são células. Se alguma das células de uma planilha for alterada, as células que se referem a essa célula também serão alteradas. É o mesmo com o FRP. Agora imagine que algumas células mudam por conta própria (ou melhor, são retiradas do mundo exterior): em uma situação de GUI, a posição do mouse seria um bom exemplo.

Isso necessariamente perde muito. A metáfora se decompõe muito rapidamente quando você realmente usa um sistema FRP. Por um lado, geralmente existem tentativas de modelar eventos discretos também (por exemplo, o mouse sendo clicado). Só estou colocando isso aqui para ter uma idéia de como é.


fonte
3
Um exemplo extremamente apropriado. É ótimo ter o material teórico, e talvez algumas pessoas obtenham as implicações disso sem recorrer a um exemplo básico, mas preciso começar com o que ele faz para mim, não com o que é abstratamente. O que eu consegui recentemente (das palestras sobre Rx da Netflix!) É que RP (ou Rx, de qualquer maneira), torna esses "valores variáveis" de primeira classe e permite que você pense sobre eles ou escreva funções que fazem coisas com eles. Escreva funções para criar planilhas ou células, se desejar. E lida com quando um valor termina (desaparece) e permite que você limpe automaticamente.
21915 Benjohn
Este exemplo enfatiza a diferença entre programação orientada a eventos e abordagem reativa, em que você apenas declara as dependências para usar o roteamento inteligente.
Kinjelom
131

Para mim, são cerca de 2 significados diferentes de símbolos =:

  1. Em matemática x = sin(t)meios, que xé nome diferente para sin(t). Então escrever x + yé a mesma coisa que sin(t) + y. A programação reativa funcional é como a matemática a esse respeito: se você escreve x + y, é calculada com qualquer valor que tseja no momento em que é usado.
  2. Nas linguagens de programação do tipo C (linguagens imperativas), x = sin(t)existe uma atribuição: significa que xarmazena o valor da sin(t) tomada no momento da atribuição.
user712092
fonte
5
Boa explicação. Eu acho que você também pode adicionar que "tempo" no sentido de FRP é normalmente "qualquer alteração de entrada externa". Sempre que uma força externa altera uma entrada de FRP, você move o "tempo" para frente e recalcula tudo novamente que é afetado pela mudança.
21415 Didier A.
4
Em matemática, x = sin(t)significa xo valor de sin(t)para o dado t. É não um nome diferente para sin(t)a função. Caso contrário, seria x(t) = sin(t).
Dmitri Zaitsev
+ Dmitri Zaitsev O sinal de igual tem vários significados em matemática. Uma delas é que sempre que você vir o lado esquerdo, poderá trocá-lo pelo lado direito. Por exemplo 2 + 3 = 5ou a**2 + b**2 = c**2.
user712092
71

OK, a partir do conhecimento de base e da leitura da página da Wikipedia para a qual você apontou, parece que a programação reativa é algo como computação de fluxo de dados, mas com "estímulos" externos específicos acionando um conjunto de nós para disparar e executar seus cálculos.

Isso é bastante adequado ao design da interface do usuário, por exemplo, no qual tocar em um controle da interface do usuário (por exemplo, o controle de volume em um aplicativo de reprodução de música) pode precisar atualizar vários itens de exibição e o volume real da saída de áudio. Quando você modifica o volume (um controle deslizante, digamos) que corresponda à modificação do valor associado a um nó em um gráfico direcionado.

Vários nós com bordas desse nó "valor do volume" seriam acionados automaticamente e quaisquer cálculos e atualizações necessários naturalmente se propagariam pelo aplicativo. A aplicação "reage" ao estímulo do usuário. A programação reativa funcional seria apenas a implementação dessa idéia em uma linguagem funcional, ou geralmente dentro de um paradigma de programação funcional.

Para saber mais sobre "computação de fluxo de dados", pesquise essas duas palavras na Wikipedia ou use seu mecanismo de pesquisa favorito. A idéia geral é a seguinte: o programa é um gráfico direcionado de nós, cada um executando um cálculo simples. Esses nós são conectados entre si por links gráficos que fornecem as saídas de alguns nós para as entradas de outros.

Quando um nó aciona ou executa seu cálculo, os nós conectados às suas saídas têm suas entradas correspondentes "acionadas" ou "marcadas". Qualquer nó com todas as entradas acionadas / marcadas / disponíveis é acionado automaticamente. O gráfico pode estar implícito ou explícito, dependendo exatamente de como a programação reativa é implementada.

Os nós podem ser vistos como disparados em paralelo, mas geralmente são executados em série ou com paralelismo limitado (por exemplo, pode haver alguns threads em execução). Um exemplo famoso foi o Manchester Dataflow Machine , que (IIRC) usou uma arquitetura de dados com tags para agendar a execução de nós no gráfico por meio de uma ou mais unidades de execução. A computação de fluxo de dados é bastante adequada para situações nas quais disparar cálculos de forma assíncrona, dando origem a cascatas de cálculos, funciona melhor do que tentar que a execução seja governada por um relógio (ou relógios).

A programação reativa importa essa idéia de "cascata de execução" e parece pensar no programa de maneira semelhante ao fluxo de dados, mas com a condição de que alguns dos nós estejam conectados ao "mundo externo" e as cascatas de execução são acionadas quando essas informações sensoriais como nós mudam. A execução do programa pareceria algo análogo a um arco reflexo complexo. O programa pode ou não ser basicamente séssil entre estímulos ou pode se estabelecer em um estado basicamente séssil entre estímulos.

A programação "não reativa" seria uma programação com uma visão muito diferente do fluxo de execução e relacionamento com entradas externas. É provável que seja um pouco subjetivo, pois as pessoas provavelmente serão tentadas a dizer qualquer coisa que responda a insumos externos "reaja" a elas. Mas, olhando para o espírito da coisa, um programa que pesquisa uma fila de eventos em um intervalo fixo e despacha todos os eventos encontrados para funções (ou threads) é menos reativo (porque apenas atende à entrada do usuário em um intervalo fixo). Novamente, é o espírito da coisa aqui: pode-se imaginar colocar uma implementação de pesquisa com um rápido intervalo de pesquisa em um sistema em um nível muito baixo e programar de forma reativa sobre ele.

Thomas Kammeyer
fonte
1
OK, há algumas boas respostas acima agora. Devo remover minha postagem? Se eu vir duas ou três pessoas dizendo que não adiciona nada, eu a excluo, a menos que sua contagem útil aumente. Não faz sentido deixá-lo aqui, a menos que acrescente algo de valor.
23610 Thomas Kammeyer
3
você mencionou o fluxo de dados, de modo que agrega algum valor ao IMHO.
21119 Rainer Joswig
Isso é o que QML deve ser, ao que parece;)
mlvljr
3
Para mim, essa resposta foi a mais fácil de entender, principalmente porque o uso de análogos naturais como "ondular através do aplicativo" e "nós do tipo sensorial". Ótimo!
Akseli Palén
1
infelizmente, o link Manchester Dataflow Machine está inoperante.
Pac0
65

Depois de ler muitas páginas sobre o FRP, finalmente me deparei com este texto esclarecedor sobre o FRP, que finalmente me fez entender o que realmente é o FRP.

Cito abaixo Heinrich Apfelmus (autor de banana reativa).

Qual é a essência da programação reativa funcional?

Uma resposta comum seria que “o FRP tem tudo a ver com descrever um sistema em termos de funções que variam no tempo, em vez de um estado mutável”, e isso certamente não estaria errado. Este é o ponto de vista semântico. Mas, na minha opinião, a resposta mais profunda e satisfatória é dada pelo seguinte critério puramente sintático:

A essência da programação reativa funcional é especificar completamente o comportamento dinâmico de um valor no momento da declaração.

Por exemplo, considere o exemplo de um contador: você tem dois botões denominados “Para cima” e “Para baixo” que podem ser usados ​​para aumentar ou diminuir o contador. Imperativamente, você deve primeiro especificar um valor inicial e depois alterá-lo sempre que um botão for pressionado; algo assim:

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

O ponto é que, no momento da declaração, apenas o valor inicial para o contador é especificado; o comportamento dinâmico do contador está implícito no restante do texto do programa. Por outro lado, a programação reativa funcional especifica todo o comportamento dinâmico no momento da declaração, assim:

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

Sempre que você quiser entender a dinâmica do contador, basta analisar sua definição. Tudo o que pode acontecer aparecerá no lado direito. Isso contrasta muito com a abordagem imperativa, na qual declarações subsequentes podem alterar o comportamento dinâmico dos valores declarados anteriormente.

Então, no meu entendimento, um programa de FRP é um conjunto de equações: insira a descrição da imagem aqui

j é discreto: 1,2,3,4 ...

fdepende, tportanto, isso incorpora a possibilidade de modelar estímulos externos

todo o estado do programa é encapsulado em variáveis x_i

A biblioteca FRP cuida do progresso do tempo, em outras palavras, do jpara j+1.

Eu explico essas equações com muito mais detalhes neste vídeo.

EDITAR:

Cerca de 2 anos após a resposta original, cheguei recentemente à conclusão de que as implementações de FRP têm outro aspecto importante. Eles precisam (e geralmente resolvem) resolver um problema prático importante: invalidação de cache .

As equações para x_i-s descrevem um gráfico de dependência. Quando algumas x_ialterações são alteradas no momento j, nem todos os outros x_i'valores j+1precisam ser atualizados, portanto nem todas as dependências precisam ser recalculadas, porque algumas x_i'podem ser independentes x_i.

Além disso, x_i-s que mudam podem ser atualizados incrementalmente. Por exemplo, vamos considerar uma operação de mapa f=g.map(_+1)no Scala, onde fe gsão Listde Ints. Aqui fcorresponde a x_i(t_j)e gé x_j(t_j). Agora, se eu acrescentar um elemento a gele, seria um desperdício realizar a mapoperação para todos os elementos g. Algumas implementações de FRP (por exemplo, reflex-frp ) visam solucionar esse problema. Esse problema também é conhecido como computação incremental.

Em outras palavras, comportamentos ( x_i-s) no FRP podem ser pensados ​​como cálculos em cache. É tarefa do mecanismo FRP invalidar e recalcular com eficiência esses cache-s (os x_i-s) se alguns deles f_imudarem.

jhegedus
fonte
4
Eu estava lá com você até você ter equações discretas . A ideia fundamental do FRP foi o tempo contínuo , onde não há " j+1". Em vez disso, pense nas funções de tempo contínuo. Como Newton, Leibniz e outros nos mostraram, muitas vezes é profundamente útil (e "natural" em sentido literal) descrever essas funções de maneira diferenciada, mas contínua, usando integrais e sistemas de EDOs. Caso contrário, você está descrevendo um algoritmo de aproximação (e um fraco) em vez da coisa em si.
Conal
A linguagem de restrições de layout e modelo em HTML layx parece expressar elementos do FRP.
@Conal isso me faz pensar em como o FRP é diferente dos ODEs. Como eles diferem?
precisa saber é o seguinte
@jhegedus Nessa integração (possivelmente recursiva, ou seja, ODEs), fornece um dos blocos de construção do FRP, não a totalidade. Todo elemento do vocabulário de FRP (incluindo, mas não limitado a, integração) é explicado com precisão em termos de tempo contínuo. Essa explicação ajuda?
Conal
29

Isenção de responsabilidade: minha resposta está no contexto de rx.js - uma biblioteca de 'programação reativa' para Javascript.

Na programação funcional, em vez de iterar através de cada item de uma coleção, você aplica funções de ordem superior (HoFs) à própria coleção. Portanto, a idéia por trás do FRP é que, em vez de processar cada evento individual, crie um fluxo de eventos (implementado com um observável *) e aplique HoFs a ele. Dessa forma, você pode visualizar o sistema como pipelines de dados que conectam editores a assinantes.

As principais vantagens do uso de um observável são:
i) ele abstrai o estado ausente do seu código, por exemplo, se você deseja que o manipulador de eventos seja acionado apenas para cada 'n' ésimo evento ou pare de disparar após os primeiros 'n', ou comece a disparar somente após os primeiros eventos 'n', você pode simplesmente usar os HoFs (filtro, takeUntil, pular respectivamente) em vez de configurar, atualizar e verificar contadores.
ii) melhora a localização do código - se você tiver 5 manipuladores de eventos diferentes alterando o estado de um componente, poderá mesclar seus observáveis ​​e definir um único manipulador de eventos no observável mesclado, combinando efetivamente 5 manipuladores de eventos em 1. Isso torna muito É fácil raciocinar sobre quais eventos em todo o sistema podem afetar um componente, pois estão todos presentes em um único manipulador.

  • Um observável é o dual de um iterável.

Um Iterable é uma sequência consumida preguiçosamente - cada item é puxado pelo iterador sempre que deseja usá-lo e, portanto, a enumeração é direcionada pelo consumidor.

Um observável é uma sequência produzida preguiçosamente - cada item é enviado ao observador sempre que é adicionado à sequência e, portanto, a enumeração é conduzida pelo produtor.

tldr
fonte
1
Muito obrigado por essa definição direta de um observável e sua diferenciação de iteráveis. Penso que é frequentemente muito útil comparar um conceito complexo com o seu conhecido conceito dual para obter uma verdadeira compreensão.
2
"Portanto, a idéia por trás do FRP é que, em vez de processar cada evento individual, crie um fluxo de eventos (implementado com um observável *) e aplique HoFs a ele." Eu poderia estar enganado, mas acredito que isso não é realmente FRP, mas uma boa abstração sobre o padrão de design do Observer, que permite operações funcionais via HoF (o que é ótimo!), Embora ainda pretenda ser usado com código imperativo. Discussão sobre o tópico - lambda-the-ultimate.org/node/4982
nqe 10/01/17
18

Cara, essa é uma ideia brilhante! Por que eu não descobri isso em 1998? Enfim, aqui está a minha interpretação do tutorial Fran . Sugestões são bem-vindas, estou pensando em iniciar um mecanismo de jogo com base nisso.

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

Resumindo: se todo componente puder ser tratado como um número, todo o sistema poderá ser tratado como uma equação matemática, certo?

Dan Ross
fonte
1
É um pouco tarde, mas de qualquer forma ... Frag é um jogo usando FRP .
ARX
14

O livro de Paul Hudak, A Escola de Expressão Haskell , não é apenas uma boa introdução a Haskell, mas também passa bastante tempo em FRP. Se você é iniciante no FRP, recomendo que você tenha uma idéia de como o FRP funciona.

Há também o que parece ser uma nova reescrita deste livro (lançado em 2011, atualizado em 2014), The Haskell School of Music .

cjs
fonte
10

De acordo com as respostas anteriores, parece que matematicamente, simplesmente pensamos em uma ordem superior. Em vez de pensar em um valor x com o tipo X , pensamos em uma função x : TX , onde T é o tipo de tempo, sejam os números naturais, os inteiros ou o continuum. Agora, quando escrevemos y : = x + 1 na linguagem de programação, na verdade queremos dizer a equação y ( t ) = x ( t ) + 1.

Yuning
fonte
9

Atua como uma planilha, conforme observado. Geralmente, com base em uma estrutura orientada a eventos.

Como em todos os "paradigmas", sua novidade é discutível.

Pela minha experiência em redes de atores de fluxo distribuído, pode facilmente ser vítima de um problema geral de consistência de estado na rede de nós, ou seja, você acaba com muita oscilação e interceptação em loops estranhos.

Isso é difícil de evitar, pois algumas semânticas implicam loops ou transmissão referenciais e podem ser bastante caóticas à medida que a rede de atores converge (ou não) em algum estado imprevisível.

Da mesma forma, alguns estados podem não ser alcançados, apesar de terem bordas bem definidas, porque o estado global se afasta da solução. 2 + 2 pode ou não chegar a 4, dependendo de quando os 2 se tornaram 2, e se eles permaneceram assim. As planilhas possuem relógios síncronos e detecção de loop. Atores distribuídos geralmente não.

Tudo muito divertido :).

emperorz
fonte
7

Este artigo de Andre Staltz é a melhor e mais clara explicação que eu já vi até agora.

Algumas citações do artigo:

A programação reativa é a programação com fluxos de dados assíncronos.

Além disso, você recebe uma incrível caixa de ferramentas de funções para combinar, criar e filtrar qualquer um desses fluxos.

Aqui está um exemplo dos diagramas fantásticos que fazem parte do artigo:

Clique no diagrama do fluxo de eventos

Gigante verde
fonte
5

Trata-se de transformações matemáticas de dados ao longo do tempo (ou ignorando o tempo).

No código, isso significa pureza funcional e programação declarativa.

Os erros do estado são um enorme problema no paradigma imperativo padrão. Vários bits de código podem alterar algum estado compartilhado em diferentes "momentos" na execução dos programas. Isso é difícil de lidar.

No FRP, você descreve (como na programação declarativa) como os dados são transformados de um estado para outro e o que os aciona. Isso permite que você ignore o tempo porque sua função está simplesmente reagindo às entradas e usando os valores atuais para criar um novo. Isso significa que o estado está contido no gráfico (ou árvore) dos nós de transformação e é funcionalmente puro.

Isso reduz enormemente a complexidade e o tempo de depuração.

Pense na diferença entre A = B + C em matemática e A = B + C em um programa. Em matemática, você está descrevendo um relacionamento que nunca mudará. Em um programa, diz que "No momento" A é B + C. Mas o próximo comando pode ser B ++, caso em que A não é igual a B + C. Em programação matemática ou declarativa, A sempre será igual a B + C, independentemente do momento em que você perguntar.

Portanto, removendo as complexidades do estado compartilhado e alterando os valores ao longo do tempo. Seu programa é muito mais fácil de raciocinar.

Um EventStream é um EventStream + alguma função de transformação.

Um comportamento é um EventStream + algum valor na memória.

Quando o evento é acionado, o valor é atualizado executando a função de transformação. O valor que isso produz é armazenado na memória de comportamentos.

Os comportamentos podem ser compostos para produzir novos comportamentos que são uma transformação em N outros comportamentos. Esse valor composto será recalculado à medida que os eventos de entrada (comportamentos) forem acionados.

"Como os observadores são apátridas, muitas vezes precisamos de vários deles para simular uma máquina de estados, como no exemplo de arrastar. Temos que salvar o estado em que é acessível a todos os observadores envolvidos, como no caminho variável acima".

Citação de - Descontinuando o padrão do observador http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

Jay Shepherd
fonte
É exatamente assim que eu me sinto em relação à programação declarativa, e você apenas descreve a ideia melhor do que eu.
21418 neevek
2

A explicação curta e clara sobre Programação Reativa aparece no Cyclejs - Programação Reativa , que usa amostras simples e visuais.

Um [módulo / Componente / objeto] é reativo significa que é totalmente responsável por gerenciar seu próprio estado, reagindo a eventos externos.

Qual é o benefício dessa abordagem? É Inversão de Controle , principalmente porque [módulo / Componente / objeto] é responsável por si mesmo, melhorando o encapsulamento usando métodos privados contra métodos públicos.

É um bom ponto de partida, não uma fonte completa de conhecimento. De lá, você pode pular para papéis mais complexos e profundos.

pdorgambide
fonte
0

Confira Rx, Extensões Reativas para .NET. Eles apontam que, com o IEnumerable, você está basicamente 'puxando' de um fluxo. As consultas Linq sobre IQueryable / IEnumerable são operações definidas que 'sugam' os resultados de um conjunto. Mas com os mesmos operadores no IObservable, você pode escrever consultas Linq que 'reagem'.

Por exemplo, você pode escrever uma consulta Linq como (de m em MyObservableSetOfMouseMovements, em que mX <100 e mY <100 selecionam novo ponto (mX, mY)).

e com as extensões Rx, é isso: você tem um código de interface do usuário que reage ao fluxo de movimentos do mouse e desenha sempre que você estiver na caixa 100.100 ...

Sentinela
fonte
0

O FRP é uma combinação de programação funcional (paradigma de programação construído sobre a idéia de que tudo é uma função) e paradigma de programação reativa (construído sobre a idéia de que tudo é um fluxo (filosofia observadora e observável)). É suposto ser o melhor dos mundos.

Confira o post de Andre Staltz sobre programação reativa para começar.

Krishna Ganeriwal
fonte