What is (functional) reactive programming?

2018-12-31 21:05发布

I've read the Wikipedia article on reactive programming. I've also read the small article on functional reactive programming. The descriptions are quite abstract.

  1. What does functional reactive programming (FRP) mean in practice?
  2. What does reactive programming (as opposed to non-reactive programming?) consist of?

My background is in imperative/OO languages, so an explanation that relates to this paradigm would be appreciated.

18条回答
其实,你不懂
2楼-- · 2018-12-31 21:17

Paul Hudak's book, The Haskell School of Expression, is not only a fine introduction to Haskell, but it also spends a fair amount of time on FRP. If you're a beginner with FRP, I highly recommend it to give you a sense of how FRP works.

There is also what looks like a new rewrite of this book (released 2011, updated 2014), The Haskell School of Music.

查看更多
栀子花@的思念
3楼-- · 2018-12-31 21:22

FRP is a combination of Functional programming(programming paradigm built upon the idea of everything is a function) and reactive programming paradigm (built upon the idea that everything is a stream(observer and observable philosophy)). It is supposed to be the best of the worlds.

Check out Andre Staltz post on reactive programming to start with.

查看更多
与风俱净
4楼-- · 2018-12-31 21:23

In pure functional programming, there are no side-effects. For many types of software (for example, anything with user interaction) side-effects are necessary at some level.

One way to get side-effect like behavior while still retaining a functional style is to use functional reactive programming. This is the combination of functional programming, and reactive programming. (The Wikipedia article you linked to is about the latter.)

The basic idea behind reactive programming is that there are certain datatypes that represent a value "over time". Computations that involve these changing-over-time values will themselves have values that change over time.

For example, you could represent the mouse coordinates as a pair of integer-over-time values. Let's say we had something like (this is pseudo-code):

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

At any moment in time, x and y would have the coordinates of the mouse. Unlike non-reactive programming, we only need to make this assignment once, and the x and y variables will stay "up to date" automatically. This is why reactive programming and functional programming work so well together: reactive programming removes the need to mutate variables while still letting you do a lot of what you could accomplish with variable mutations.

If we then do some computations based on this the resulting values will also be values that change over time. For example:

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

In this example, minX will always be 16 less than the x coordinate of the mouse pointer. With reactive-aware libraries you could then say something like:

rectangle(minX, minY, maxX, maxY)

And a 32x32 box will be drawn around the mouse pointer and will track it wherever it moves.

Here is a pretty good paper on functional reactive programming.

查看更多
残风、尘缘若梦
5楼-- · 2018-12-31 21:25

After reading many pages about FRP I finally came across this enlightening writing about FRP, it finally made me understand what FRP really is all about.

I quote below Heinrich Apfelmus (author of reactive banana).

What is the essence of functional reactive programming?

A common answer would be that “FRP is all about describing a system in terms of time-varying functions instead of mutable state”, and that would certainly not be wrong. This is the semantic viewpoint. But in my opinion, the deeper, more satisfying answer is given by the following purely syntactic criterion:

The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration.

For instance, take the example of a counter: you have two buttons labelled “Up” and “Down” which can be used to increment or decrement the counter. Imperatively, you would first specify an initial value and then change it whenever a button is pressed; something like this:

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

The point is that at the time of declaration, only the initial value for the counter is specified; the dynamic behavior of counter is implicit in the rest of the program text. In contrast, functional reactive programming specifies the whole dynamic behavior at the time of declaration, like this:

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

Whenever you want to understand the dynamics of counter, you only have to look at its definition. Everything that can happen to it will appear on the right-hand side. This is very much in contrast to the imperative approach where subsequent declarations can change the dynamic behavior of previously declared values.

So, in my understanding an FRP program is a set of equations: enter image description here

j is discrete: 1,2,3,4...

f depends on t so this incorporates the possiblilty to model external stimuli

all state of the program is encapsulated in variables x_i

The FRP library takes care of progressing time, in other words, taking j to j+1.

I explain these equations in much more detail in this video.

EDIT:

About 2 years after the original answer, recently I came to the conclusion that FRP implementations have another important aspect. They need to (and usually do) solve an important practical problem: cache invalidation.

The equations for x_i-s describe a dependency graph. When some of the x_i changes at time j then not all the other x_i' values at j+1 need to be updated, so not all the dependencies need to be recalculated because some x_i' might be independent from x_i.

Furthermore, x_i-s that do change can be incrementally updated. For example let's consider a map operation f=g.map(_+1) in Scala, where f and g are List of Ints. Here f corresponds to x_i(t_j) and g is x_j(t_j). Now if I prepend an element to g then it would be wasteful to carry out the map operation for all the elements in g. Some FRP implementations (for example reflex-frp) aim to solve this problem. This problem is also known as incremental computing.

In other words, behaviours (the x_i-s ) in FRP can be thought as cache-ed computations. It is the task of the FRP engine to efficiently invalidate and recompute these cache-s (the x_i-s) if some of the f_i-s do change.

查看更多
长期被迫恋爱
6楼-- · 2018-12-31 21:26

According to the previous answers, it seems that mathematically, we simply think in a higher order. Instead of thinking a value x having type X, we think of a function x: TX, where T is the type of time, be it the natural numbers, the integers or the continuum. Now when we write y := x + 1 in the programming language, we actually mean the equation y(t) = x(t) + 1.

查看更多
栀子花@的思念
7楼-- · 2018-12-31 21:26

It is about mathematical data transformations over time (or ignoring time).

In code this means functional purity and declarative programming.

State bugs are a huge problem in the standard imperative paradigm. Various bits of code may change some shared state at different "times" in the programs execution. This is hard to deal with.

In FRP you describe (like in declarative programming) how data transforms from one state to another and what triggers it. This allows you to ignore time because your function is simply reacting to its inputs and using their current values to create a new one. This means that the state is contained in the graph (or tree) of transformation nodes and is functionally pure.

This massively reduces complexity and debugging time.

Think of the difference between A=B+C in math and A=B+C in a program. In math you are describing a relationship that will never change. In a program, its says that "Right now" A is B+C. But the next command might be B++ in which case A is not equal to B+C. In math or declarative programming A will always be equal to B+C no matter what point in time you ask.

So by removing the complexities of shared state and changing values over time. You program is much easier to reason about.

An EventStream is an EventStream + some transformation function.

A Behaviour is an EventStream + Some value in memory.

When the event fires the value is updated by running the transformation function. The value that this produces is stored in the behaviours memory.

Behaviours can be composed to produce new behaviours that are a transformation on N other behaviours. This composed value will recalculate as the input events (behaviours) fire.

"Since observers are stateless, we often need several of them to simulate a state machine as in the drag example. We have to save the state where it is accessible to all involved observers such as in the variable path above."

Quote from - Deprecating The Observer Pattern http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

查看更多
登录 后发表回答