Skip to content

Instantly share code, notes, and snippets.

@Raynos
Created October 14, 2014 17:58
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Raynos/981e8f8d0e1d069e5590 to your computer and use it in GitHub Desktop.
Save Raynos/981e8f8d0e1d069e5590 to your computer and use it in GitHub Desktop.

What is an FRP application.

An FRP application has the following properties

  • Functional, pure, immutable.
  • Reactive, your application is a DAG, you react to "core" inputs and from new outputs.

Being an immutable DAG

If your application is an immutable DAG it has certain properties

  • Each conceptual node in the DAG is the sole owner of truth for that information.
  • A DAG can be serialized, have time rewound on it, can go through an interactive debugger, etc.
  • Parts of your application only change by reacting to input, you know exactly what can cause state changes in any part of your application by looking at which inputs you react to.

Purity and the DAG

The meat of your application, inputs, update, state and render is pure. This means it consists of plain functions that do not do IO nor have effects.

The actual IO or effectful part of your application is completely at the edges of your DAG. This means you can easily test your application because it's just logic. You can easily run your application in a web worker because it's just logic. You can easily do introspection on your application or time rewinding because doing so has no effects.

Removing implementation complexity.

When building GUI progress with a DAG you can take "raw" input from various sources (events, mouse, keyboard) and convert them into actual logical, well typed "user intents" that are agnostic of the implementation details of your host environment. The same applies for output, you can create a well typed structural representation of your UI and have it be rendered in the host environment.

You get to choose what type of structural representation you want for the visual UI and you get to choose what kind of user intents you want to operate upon. Your application is shielded from the implementation details of the Host environment and they do not corrupt their way into your application.

@staltz
Copy link

staltz commented Oct 14, 2014

I already knew Rx wasn't FRP, and also in your definition it still isn't. And that's because of the first bullet point: "Functional, pure, immutable.". Rx allows the programmer to create side effects and mutate things. But it doesn't require to write code in a side effectful way always. The programmer can choose to write code in a mostly purely functional style, and sometimes take shortcuts for mutation and side effects.

I've been preparing an architecture (and soon, perhaps, framework) for JS single page apps that share a lot in common Mercury, MV*, React, unidirectionality, etc, but has some FRP properties that you described here. It's for now called MVI: Model-View-Intent and is based on and dependent on Rx Observables.

MVI is a (circular) cyclic graph, so definitely not DAG already. Each M, V, and I are "function-like" in the sense that they have inputs and outputs, but are not a javascript function strictly speaking.

Model: takes interaction intents (type Rx.Observable) as inputs. Outputs an Observable of data, in other words, a stream of the "state" changing over time.
View: takes the model data Observable as input, and outputs two things: an Observable of vtree, and Observables for raw input events coming from the DOM.
Intent: takes raw input events as input, and outputs an Observable of intended changes to the model. This can be understood as an "interpreter" that translates raw user inputs to "delta model" (a diff of the model). You could also decouple this concept from the model, and make it agnostic as you said.

Notice the circular dependency. That's why one cannot do, for instance:

var m = model(i);
var v = view(m);
var i = intent(v);

Instead, it goes like this (approximately):

var m = model();
var v = view();
var i = intent();
m.observe(i);
v.observe(m);
i.observe(v);

The trick here is that the View does not render anything. It only exports an Observable stream of vtree changing over time. You can then have another component called Renderer who's only responsibility is to observe all vtree Observables of all Views, and push (diff / patch) the vtrees to the DOM.

MVI has these properties:

  • Unidirectional
  • Really simple one-way rendering from model data to vtree (happening inside View observables)
  • The cyclic MVI graph is purely functional (except the programmer can choose not to do pure)
  • "You know exactly what can cause state changes in any part of your application by looking at which inputs you react to."

MVI is new to me, so I haven't built anything big with it, but at the moment I have two ongoing projects being written in MVI. I'm very positive Rx can scale to large applications without introducing inter-component complexity, that's based on my experience with Rx and an MVVM architecture on Android. But so far I can't see a theoretical obstacle to MVI scaling to larger applications.

@staltz
Copy link

staltz commented Oct 14, 2014

PS: I know that in this intro I said Rx was FRP, but later I learned my lesson that it isn't. I still find it hard to categorize Elm's and Rx's similarities, though...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment