|
2 | 2 | title: FRP
|
3 | 3 | ---
|
4 | 4 |
|
5 |
| -# FRP |
| 5 | +# Functional Reactive Programming and Modular Comprehensibility |
6 | 6 |
|
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." |
9 | 8 |
|
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> |
11 | 10 |
|
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. |
13 | 12 |
|
14 |
| - |
| 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? |
15 | 14 |
|
16 |
| -In the Elm Architecture, you define: |
| 15 | +## Related issues |
17 | 16 |
|
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. |
21 | 18 |
|
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?! |
23 | 20 |
|
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)] |
25 | 22 |
|
| 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 | +>  |
| 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 | +>  |
| 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 | +>  |
| 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 | + |
| 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 | + |
| 114 | + |
| 115 | +If we’re looking to understand a single 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. |
| 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. |
26 | 186 |
|
27 | 187 | ## ToDo
|
28 | 188 |
|
29 | 189 | * 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? |
33 | 192 | * find a real life open-source UI app as an example?
|
34 | 193 | * https://github.com/rtfeldman/elm-spa-example
|
35 | 194 | * https://github.com/ravichugh/sketch-n-sketch
|
36 | 195 | * https://github.com/ellie-app/ellie
|
37 | 196 |
|
38 | 197 |
|
39 |
| - |
40 |
| - |
41 |
| - |
42 | 198 | <script>
|
43 | 199 |
|
44 | 200 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
0 commit comments