Skip to content

Commit 7156820

Browse files
author
Steve Krouse
committed
## Draft 2 of FRP Essay
I got started this morning at 7am and worked until now (4pm) with only two breaks, a 90-minute and a 30-minute one, which means I worked on this for 7 hours today!! I'm very proud of myself. I'm also excited about how the piece is coming together. I feel my understanding of these topics deepening as I research them more fully in order to write the piece. One thing in particular that I came across is the [imitate](https://github.com/staltz/xstream#-imitatetarget) method in CycleJS's xstream, which makes me wonder how far I can get with an FRP framework in vanilla JavaScript... It'd be neat if I didn't have to build a whole runtime! I was only planning to work on this until noon-ish, then run, and then work on my UK visa application, but I didn't do it that way. I'm going to go run now. If I have energy after that, maybe I'll do my visa app, but if not I'll do that tomorrow. I also have to do my Dark work for next week tomorrow, because I'm going to be out of town this weekend, so I likely won't work on this again until Monday, or more likely, Tuesday of next week.
1 parent 1ff9c47 commit 7156820

File tree

1 file changed

+174
-18
lines changed

1 file changed

+174
-18
lines changed

drafts/frp.md

Lines changed: 174 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,199 @@
22
title: FRP
33
---
44

5-
# FRP
5+
# Functional Reactive Programming and Modular Comprehensibility
66

7-
Explicit data dependencies are essential to comprehendable code. This is particularly relevant in user interface code, where state is notoriously complex.
8-
In the event-driven paradigm, code is organized around events. This makes data dependencies are hopelessly implicit, because only the initial value of a piece of data is explicitly defined, and after initialization, any line of code can mutate any piece of data in its scope. If you are trying to understand how a single piece of data changes, you are doomed to read every line in its scope and re-create possible control flow paths in your head.
7+
If you wanted to understand the entirety of a software project, you'd have to read every line of code. But what if you only wanted to understand a single module? Can you get away with only reading a similarly smally piece of the code? In other words, is module comprehension proportional to the amount of code read? We can refer to such a project as "modularly comprehensible."
98

10-
In the Functional Reactive Programming (FRP) paradigm, as originally conceived by Conal Elliott and Paul Hudak, code is organized around pieces of data, and events are subordinated as explicit dependencies of data as infinite streams. Explicit data dependencies, in the form of other piece of data and event streams, allow you to comprehend the *entire* behavior of specific pieces of data, simply by reading their definitions, recursively. This allows you to *categorically rule out all the code you do not have to read* in order to comprehend the relevant data.
9+
<iframe src="https://www.desmos.com/calculator/2dfy7ke1mp?embed" width="500px" height="500px" style="border: 1px solid #ccc" frameborder=0></iframe>
1110

12-
The FRP-inspired user interface frameworks of today have given up explicit data dependencies in favor of the Elm Architecture, which has been adopted in many frameworks, including React Redux, VueX, and CycleJS.
11+
Most programming languages are not modularly comprehensible in such a linear fashion but in an exponential one, as demonstrated in the yellow curve above, where understanding is dim until you've read almost all the code, at which point your understanding expands very quickly.
1312

14-
![image](https://user-images.githubusercontent.com/2288939/42588010-76de6134-850a-11e8-9de5-ed2320a07375.png)
13+
The lack of modular comprehensibility slows down the time it takes a programmer to make a change to an unfamiliar project. This is particularly relevant in open-source software, because developers have limited time to contribute. Have you ever wanted to make a small bug-fix or improvement to an open-source project, but gave up after a few hours of failing to understand how the code works?
1514

16-
In the Elm Architecture, you define:
15+
## Related issues
1716

18-
* the type of your data and its initial values (the model),
19-
* the view in terms of your model, and how various events map to "actions" (the HTML),
20-
* and "a global reducer" to take the current model and an action, and product an updated model (the update).
17+
Of course, the modularity of comprehensibility is only one piece of code comprehensibility.
2118

22-
The Elm Architecture gives us explicit clarity only on our state’s initial values and on how actions update state. Yet any action can affect any piece of state. And any view element can emit any action. So if we’re looking to understand a piece of state, we’re not much better off than with an entirely imperative framework: we still have to read more-or-less the whole application even if we wish only to comprehend only a small piece.
19+
Another big piece is understanding how the files, folders, import statements, and build tools and parsed together into a unifed program. Where's the entry point for goodness sake?!
2320

24-
Contrast this with Reflex...
21+
Another impediment to code comprehensibility is code's symbolic, abstract nature. In [A Human-Readable Interactive Representation of a Code Library](http://glench.github.io/fuzzyset.js/ui/), Glen Chiacchieri makes a library more understandale through editable concrete values at various stages of code execution. Glen's thesis that concrete values are key to comprehension is backed by research that found that "to comprehend programs, developers need to acquire runtime information [to inspect the state of the application]. For this, developers frequently execute the application using a debugger." [[Walid Maalej, Rebecca Tiarks, Tobias Roehm, and Rainer Koschke. 2014. On the Comprehension of Program Comprehension](https://dl.acm.org/citation.cfm?id=2622669)]
2522

23+
And of course, code comprehensibility is only a small part of making a change to a project, which also includes getting the code to compile and run on your machine, editing the code, and testing your changes - not to mention the social complexities of getting your pull request merged into the main project (which involves getting your code reviewer to *comprehend* your changes).
24+
25+
However, the scope of this post is limited to the modularity of code comprehension.
26+
27+
## Module dependencies
28+
29+
Code is often not modularly comprehensible because the way dependencies between modules are organized.
30+
31+
In [Reactive MVC and The Virtual DOM](https://web.archive.org/web/20180530055638/https://futurice.com/blog/reactive-mvc-and-the-virtual-dom), Andre Staltz discusses the trade-offs between the Interactive (or imperitive) pattern and Reactive patterns.
32+
33+
> ![image](https://user-images.githubusercontent.com/2288939/42631117-35144a28-85a7-11e8-877e-552732396590.png)
34+
>
35+
> An arrow from foo to bar means that foo somehow affects bar, by updating data in bar. A typical case is code inside foo which calls
36+
>
37+
> `bar.updateSomething(someValue);`
38+
>
39+
> Question: where does each arrow live in a program? They can't simply live in between modules, because all code is inside some module. The answer is: it depends, but typically, you expect the arrow to be defined in the arrow's tail, as such:
40+
>
41+
> ![image](https://user-images.githubusercontent.com/2288939/42631121-3789f186-85a7-11e8-893f-6357c6aa8864.png)
42+
>
43+
> In the Interactive pattern, module X defines which other modules X affects.
44+
>
45+
> The dual of Interactive is Reactive, where arrows are defined in the opposite end, in the arrow head, as such:
46+
>
47+
> ![image](https://user-images.githubusercontent.com/2288939/42631130-39df12ae-85a7-11e8-8ce3-ed48235efeb4.png)
48+
>
49+
> In the Reactive pattern, module X defines which other modules affect X.
50+
>
51+
> The benefit of Reactive over Interactive is mainly separation of concerns. In Interactive, if you want to discover what affects X, you need to search for all such calls `X.update()` in other modules. However, in Reactive, all that it takes is to peek inside X, since it defines everything which affects it. For instance, this property is common in spreadsheet calculations. The definition of the contents of one cell are always defined just in that cell, regardless of changes happening on the other cells it depends on.
52+
53+
Note on the word "module": In his example above, Staltz uses JavaScript files as representative of modules, but you could replace the `foo.js` filename with `foo` the variable or function name for similar result. Modular comprehensibility has nothing to do with browserify modules. In other words, I'm using the dictionary definition of module: "any of a number of distinct but interrelated units from which a program may be built up or into which a complex activity may be analyzed."
54+
55+
I believe that Staltz understates the non-modularity of the Interactive pattern. Let's say you wish to understand the behavior of X, so you grep for `X.update` in all modules. Now you have to go to the call site in each module and understand that module well enough to know what triggers that line of code, and what the values of the arguments it will pass it. If you're lucky, all that information is self-contained in that module, but you'll likely be forced to understand yet more modules to understand how *this* module affects X.
56+
57+
Explicit dependencies are a huge win for modular comprehensibility. For example, consider the program `A` below, which is explicitly defined in terms of its modules below. In this picture read an arrow from B to A as "A is defined in terms of B" or "B is an explicit depdency of A."
58+
59+
<iframe src="https://mermaidjs.github.io/mermaid-live-editor/#/view/eyJjb2RlIjoiXG5ncmFwaCBCVFxuICAgIFxuQi0tPkFcbkMtLT5BXG5ELS0-QVxuRS0tPkFcbkYtLT5CXG5HLS0-QlxuSC0tPkJcbkktLT5DXG5KLS0-Q1xuSy0tPkRcbkwtLT5FXG5NLS0-RVxuTi0tPkVcbk8tLT5FXG5LLS0-TlxuUS0tPk5cbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19" width="500px" height="320px" style="border: 1px solid #ccc" frameborder=0></iframe>
60+
61+
If you want to understand a module, you have to understand it's modules, recursively. So if you want to understand the entire program, A, you have to read everything. However, let's say you were only interested in module E. In this case you only have to read the children of E, highlighted below:
62+
63+
<iframe src="https://mermaidjs.github.io/mermaid-live-editor/#/view/eyJjb2RlIjoiXG5ncmFwaCBCVFxuICAgIFxuQi0tPkFcbkMtLT5BXG5ELS0-QVxuRS0tPkFcbkYtLT5CXG5HLS0-QlxuSC0tPkJcbkktLT5DXG5KLS0-Q1xuSy0tPkRcbkwtLT5FXG5NLS0-RVxuTi0tPkVcbk8tLT5FXG5LLS0-TlxuUS0tPk5cblxuY2xhc3NEZWYgY2xhc3NOYW1lIGZpbGw6eWVsbG93LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDo0cHg7XG5jbGFzcyBFLEwsTSxOLE8sUSxLIGNsYXNzTmFtZTtcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19" width="500px" height="320px" style="border: 1px solid #ccc" frameborder=0></iframe>
64+
65+
Explicit data dependencies allow you to comprehend modules by reading their definitions, recursively. This allows you to *categorically rule out all the modules you do not have to read* in order to comprehend the relevant module. If a module is not an explicit dependency (or dependency of a dependency...), it's not relevant. In fact, it's explicitly *independent*.
66+
67+
## Related work
68+
69+
TODO program slicing and program paths
70+
71+
## Restricting the model
72+
73+
In order to enforce explicit dependencies, we must restrict our programming model by disallowing side-effects, such a mutable state, leaving us with only pure functions, and a langauge in the spirit of Haskell.
74+
75+
TODO need to defend this restriction.
76+
77+
This works beautifully for batch software that accept an input and return an output, but does not scale on its own to interactive software that responds to inputs over time. That's where FRP comes in.
78+
79+
## FRP and the Elm Architecture
80+
81+
"Functional Reactive Programming is a way of writing software using only pure functions." [[Ryan Trinkle](https://www.youtube.com/watch?v=dOy7zIk3IUI?t=1m26s)]
82+
83+
FRP is particularly useful for constructing software interfaces. The popular UI framworks of today, such as ReactJS, are based on FRP principles.
84+
85+
However, most web-based FRP frameworks share a data model that does not feature modular comprehensibility. The data model they share was originally concieved as for the Elm programming language, a pure langauge in the spirit of Haskell, and entitled "The Elm Architecture." It inspired ReactJS's Redux, VueJS's Vuex, CycleJS's Onionify, among many others.
86+
87+
The core of the architecture is a reducer function that takes the old state, and an event, and returns a newly computed state.
88+
89+
```haskell
90+
reducer :: state -> event -> state
91+
```
92+
93+
<iframe src="https://mermaidjs.github.io/mermaid-live-editor/#/view/eyJjb2RlIjoiXG5ncmFwaCBURFxuIFxucmVkdWNlci0tPnN0YXRlXG5zdGF0ZSAtLT4gdmlldyBcbnZpZXctLT58ZXZlbnR8cmVkdWNlclxuIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0" width="500px" height="320px" style="border: 1px solid #ccc" frameborder=0></iframe>
94+
95+
The "view" (HTML and CSS) of the application is defined declaratively in terms of `state`.
96+
97+
The way the view sends events to the `reducer` differs between the frameworks. For example, React and Elm generates events from *within* view, such as `<button onClick={this.handleClick()}>` or `button [ onClick Increment ]`, respectively, while CycleJS derives event information *outside* the view, such as `const clicks = DOM.select('button').events('click')`.
98+
99+
Additionally, these frameworks sometimes wrap events in semantic labels called "actions" or "messages". For example, an `onClick` event could become a `Submit` action. This will become clearer in the Elm example below.
100+
101+
In the Elm Architecture, we are explicit about the initial value of state, how the state determines the view, and how various events effect the state. This is a huge improvement over the non-FRP frameworks of the past, such as JQuery. Benefits include serializable state, which gives us hot-swapping and time travel debugging!
102+
103+
However, let's examine the Elm Architecture from a modular comprehensibility point of view. Any message can modify any state. In the reducer, called `update`, in [Elm ToDoMVC](https://github.com/evancz/elm-todomvc/blob/master/Todo.elm), the `Add` message triggers an update of three different pieces of state:
104+
105+
![image](https://user-images.githubusercontent.com/2288939/42649315-6113b890-85d7-11e8-88fb-184081fe3ba5.png)
106+
107+
This means that you can't modularly gain a complete understanding of any piece of state. You have to look through *all* the messages for any piece of state.
108+
109+
There's also a subtler way this undermines explicit dependencies: each piece of state can be modified in terms of *any other piece of state*. There's no explict isolation between independent states.
110+
111+
Additionally, any view element can emit any message. Again from [Elm ToDoMVC](https://github.com/evancz/elm-todomvc/blob/master/Todo.elm), the viewInput's `onInput` and `onEnter` events send `UpdateField` and `Add` messages, respectively:
112+
113+
![image](https://user-images.githubusercontent.com/2288939/42649332-6cf319c6-85d7-11e8-96c4-0c11a19cc67c.png)
114+
115+
If were looking to understand a single piece of state, were not much better off than with an entirely imperative framework: we still have to read more-or-less the whole application even if we wish only to comprehend only a small piece.
116+
117+
However, this is a feature as well as a bug. The Elm Architecture purposefully designed to limit the explicitness of dependencies. In [Accidentally Concurrent](https://youtu.be/DfLvDFxcAIA?t=27m32s), the creator of Elm Evan Czaplicki explains how he things explicit dependencies can lead to crazy incomprehensibility:
118+
119+
<img src="https://user-images.githubusercontent.com/2288939/42652385-3f6d0454-85e0-11e8-98b6-ce4d1b48bf16.png" width="500px">
120+
121+
However, you and I, dear reader, are not scared off my explicit dependencies, because they are what allows us to know what code we must read and what code we can safely avoid. If you focus on a single circle at a time, you can easily see what it depends upon. (Except maybe for some of those nasty-looking cyclic states. We'll come back to those below.)
122+
123+
## Synchronous dataflow and higher-order streams
124+
125+
As it turns out, the FRP-inspired libaries we've discussed so far aren't truly representative of FRP as it was originally concieved. In Elm 0.17, Elm said [A Farewell to FRP](http://elm-lang.org/blog/farewell-to-frp):
126+
127+
> So is Elm about FRP anymore? No. Those days are over now. Elm is just a functional language that takes concurrency very seriously. And from a user's perspective, Elm is just a friendly functional language!
128+
> Interested readers may find Lucid Synchrone interesting. Unfortunately for me, I had no idea my thesis had so much in common with synchronous programming languages at the time, but the connections are quite striking. I might argue that Elm was *never* about FRP.
129+
130+
The main distinction between [OG](https://www.urbandictionary.com/define.php?term=OG) FRP and Synchronous dataflow langaugse is whether they allow higher-order streams. Because Eve, ReactJS, and VueJS do not, they are closer in spirit to dataflow langauges such as Esterel and Lucid Synchrone, than they are to the OG FRP frameworks, such as Fran. (CycleJS does allow higher-order streams.)
131+
132+
## [OG](https://www.urbandictionary.com/define.php?term=OG) FRP
133+
134+
TODO
135+
136+
behaviors and events (streams)
137+
138+
I like to think about FRP as "zooming out." code is organized around pieces of data, and events are subordinated as explicit dependencies of data as infinite streams.
139+
140+
space time issues
141+
142+
## Cyclic FRP
143+
144+
Cycles are a major concern with explicit dependencies. For one, most FRP libraries in the wild do not support them. The only one I was able to find was Reflex, which is not nearly as popular as the other frameworks discussed.
145+
146+
But even if cyclic FRP is possible, is it desirable? As we saw above in Evan Czaplicki's crazy picture, things can get hairy fast. In fact, it may seem like things are *too explicit*, too tightly coupled.
147+
148+
I, however, would push back on this concern. I believe that if the essential, inherent nature of a module is that it's cyclically dependent with another module, we should explicitly expose that nature in the structure of the code.
149+
150+
TODO maybe talk about https://en.wikipedia.org/wiki/Connascence
151+
152+
Let's examine my favorite simple cyclic dependency: a button that counts its clicks. This is one way to do it in Reflex:
153+
154+
```haskell
155+
clickEvents <- intButton clicksCount
156+
clicksCount <- count clickEvents
157+
```
158+
159+
First look at `intButton clicksCount`. This is where the button is created with the number of clicks as it's text.
160+
161+
`intButton` is a helper function of type `Show a => Dynamic a -> m (Event t ())`. In other words, it inputs a dynamic value (a stream) that can be coerced to a string (that's what the `Show a =>` part means), creates a button on the screen with that value, and returns an event stream (which in this case is just click events).
162+
163+
`clicksCount` is simply counting the number of events within the `clickEvents` stream.
164+
165+
Here's the same program in Elm:
166+
167+
```elm
168+
-- model
169+
count = 0
170+
171+
--reducer
172+
update msg count =
173+
case msg of
174+
Increment ->
175+
count + 1
176+
177+
-- view
178+
view count = intButton [ onClick Increment ] count
179+
```
180+
181+
Yes there's less coupling. But is that always a good thing? No, there is such a thing as too little coupling.
182+
183+
## Visualizing higher-order, cyclic FRP
184+
185+
Visualizing cyclic dependencies in a comprehensible way will be a challenge. I will freely admit that if it ultimately proves impossible to visualize cyclic dependencies in a comprehensible way, FRP may not be the silver bullet for modular comprehensibility I hope it can be.
26186

27187
## ToDo
28188

29189
* title ideas
30-
* FRP and Explicit Data Dependencies
31-
* be more concrete - have specific examples to articulate points above
32-
* maybe make the article in terms of "modular understanding" instead of "comprehsibility"?
190+
* FRP and Explicit Data Dependencies
191+
* Reflex TodoMVC?
33192
* find a real life open-source UI app as an example?
34193
* https://github.com/rtfeldman/elm-spa-example
35194
* https://github.com/ravichugh/sketch-n-sketch
36195
* https://github.com/ellie-app/ellie
37196

38197

39-
40-
41-
42198
<script>
43199

44200
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

0 commit comments

Comments
 (0)