Skip to content

Commit e8d146f

Browse files
author
Steve Krouse
committed
## Added more example code to FRP essay
I added a lot to the essay today. It really feels close, and like it's shaping up. I am aiming to get a completed Draft 2 to Jonathan Edwards by this upcoming Monday. I added counter button examples to explain Elm and Reflex, and then some ToDoMVC code from each to get into the comparison. I could definitely come up with better example apps (larger would be better), and another way to improve the comparison could be to articulate the Elm Architecture in Reflex (which is supported) in order to keep the syntax more uniform, but I don't have the time for that, especially considering how impossible it is to code in Reflex - at least with the set up I have. I had a VERY FUSTRATING hour or two today when I was working with ghcjs and Reflex and emacs in my remote shell. My god I hated it. I had to take a break because I felt upset afterwards for an hour or so. I wasn't prepared for how annoying it all was. Such a slooow feedback loop. And so fustrating using emacs! For example, copying and pasting code doesn't work because the formatting gets messed up. Just one example of how everything takes longer. In terms of what's left, I have to finish explaining the meat of Reflex TodoMVC, and then maybe explain one other part of Reflex ToDoMVC (should only take another 3 hours), and then write out the "is the cure worse than the disease" section (~2 hours), and tie it all together with the related work, conclusion and a final read through and edit (~2 hours). I should be able to spend some time tomorrow and Sunday on this, so we're in good shape for getting it done by Monday. (In other news, last night at 11:30pm I had [some exciting ideas for a future research topic](https://twitter.com/stevekrouse/status/1027765242217029632).)
1 parent 2363138 commit e8d146f

File tree

1 file changed

+160
-34
lines changed

1 file changed

+160
-34
lines changed

drafts/frp.md

Lines changed: 160 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,166 @@ This paper contrasts The Elm Architecture with a state management pattern that m
2525

2626
## 2. The Elm Architecture
2727

28-
Elm is a pure functional language in the spirit of Haskell. It compiles to JavaScript. It is an FRP-inspired langauge that only allows first-order and non-cyclic streams for reasons discussed in [TODO disease worse than cure?].
28+
Elm is a pure functional language in the spirit of Haskell. It compiles to JavaScript. It is an FRP-inspired langauge that only allows first-order and non-cyclic streams for reasons discussed in Section 5.
2929

3030
The Elm Architecture follows directly from Elm's first-order, non-cyclic nature: because streams cannot contain other streams, nor be cyclic, the only way to construct user interfaces, which are inherently cyclical, is to have a single, large, language-supported cycle.
3131

32-
<iframe width="500" height="300" src="https://mermaidjs.github.io/mermaid-live-editor/#/view/eyJjb2RlIjoiXG5ncmFwaCBURFxuIFxucmVkdWNlci0tPiBzMihuZXcgc3RhdGUpXG5zMihuZXcgc3RhdGUpIC0tPiB2aWV3IFxudmlldy0tPnxldmVudCwgb2xkIHN0YXRlfHJlZHVjZXJcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19" frameborder="0" allowfullscreen></iframe>
32+
<iframe width="500" height="300" src="https://mermaidjs.github.io/mermaid-live-editor/#/view/eyJjb2RlIjoiXG5ncmFwaCBURFxuIFxudXBkYXRlLS0-IHMyKG5ldyBtb2RlbClcbnMyKG5ldyBtb2RlbCkgLS0-IHZpZXcgXG52aWV3LS0-fG1lc3NhZ2UsIG9sZCBtb2RlbHx1cGRhdGVcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19" frameborder="0" allowfullscreen></iframe>
3333

34-
The core of the architecture is its a compound state value. It represents the entirety of an applications state at any given time. The reducer steps the state forward in response to events, thus simulating mutable variables that change over time. In this way, the Elm Architecture simulates the "primitive word-at-a-time style of program-ming inherited from the von Neumann computer ... instead of encouraging us to think in terms of the larger conceptual units of the task at hand." [Can Programming Be Liberated...?]
34+
Let's explore the architecture with a simple counter application. The full code can be [viewed here](https://gist.github.com/stevekrouse/5d763977c620c590d9f3434231348c76) and [edited in a live here](https://ellie-app.com/32jds3Yw2Nda1). The output of the application is:
3535

36-
```haskell
37-
reducer :: state -> event -> state
36+
<button onclick="num.innerText-=-1">+1</button>
37+
<div id="num">0</div>
38+
<button onclick="num.innerText-=1">-1</button>
39+
40+
The core of the architecture is its a compound state value, called the "model". It represents the entirety of an applications state at any given time.
41+
42+
```elm
43+
type alias Model =
44+
{ count : Int }
3845
```
3946

40-
The view is a pure function of state. The way the view sends events to the `reducer` differs between frameworks. CycleJS derives event information *outside* the view, such as `const clicks = DOM.select('#counter-button').events('click')`. React/Redux and Elm generate events from *within* view, such as `<button onClick={this.handleClick()}>` or `button [ onClick Increment ]`, respectively. Additionally, these frameworks sometimes wrap events in semantic labels called "actions" or "messages". For example, an `onClick` event can be labeled as the `Increment` action. This will become clearer in the Elm example below.
47+
Just like in imperitive programming, the Elm Architecture is explict only about the *initial* values of the model.
4148

42-
Just like in imperitive programming, the Elm Architecture is explict only about the *initial* values of states.A ny 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:
49+
```elm
50+
initialModel : Model
51+
initialModel =
52+
{ count = 0 }
53+
```
4354

44-
![image](https://user-images.githubusercontent.com/2288939/42886488-ab1c24c4-8a71-11e8-92f5-13dc2f282ad4.png)
55+
The reducer steps the model forward in response to messages, thus simulating mutable variables that change over time.
4556

46-
You can't gain a complete understanding of any piece of state by looking in one place. You have to look through *all* messages to see if and how each affects the state in question.
57+
```elm
58+
type Msg
59+
= Increment
60+
| Decrement
4761

48-
There's also a subtler way this undermines explicitness: each piece of state can be modified in terms of *any other piece of state*. There's no explicit isolation between independent states.
62+
update : Msg -> Model -> Model
63+
update msg model =
64+
case msg of
65+
Increment ->
66+
{ model | count = model.count + 1 }
4967

50-
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:
68+
Decrement ->
69+
{ model | count = model.count - 1 }
70+
```
5171

52-
![image](https://user-images.githubusercontent.com/2288939/42886260-13e14bb6-8a71-11e8-8961-044c1a596b8a.png)
72+
The view is a pure function of state. Messages are generated from events, such as the `Increment` and `Decrement` messages below, both from `onClick` events.
5373

54-
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.
74+
```elm
75+
view : Model -> Html Msg
76+
view model =
77+
div []
78+
[ button [ onClick Increment ] [ text "+1" ]
79+
, div [] [ text <| toString model.count ]
80+
, button [ onClick Decrement ] [ text "-1" ]
81+
]
82+
```
83+
84+
This examples uses the `Html.beginnerProgram` to tie all the pieces together:
85+
86+
```elm
87+
main : Program Never Model Msg
88+
main =
89+
Html.beginnerProgram
90+
{ model = initialModel
91+
, view = view
92+
, update = update
93+
}
94+
```
5595

5696
## 3. Reflex
5797

5898
The Reflex library was built for Haskell web development via ghcjs. It features higher-order and cyclic streams, which means that streams can contain streams, and streams can reference streams that reference themselves. It is these features that are neccesary to maintain explicitness in FRP.
5999

60-
* compare ToDoMVC to Elm
61-
* what does foldDyn do and what's its type? Show how the stream is higher order.
100+
Like in traditional FRP, Reflex has two main concepts: Events and Behaviors. Events are discrete occurances in time, while behaviors are continuously defined values for all points in time. For implementation reasons, Reflex also has another key concept, a Dynamic value, which has the properties of both Events and Behaviors, in that it is defined in all points in time as well as being able to notify you at the discrete points in time when it changes.
101+
102+
Here is the same button counter application in Reflex:
103+
104+
```haskell
105+
bodyElement :: MonadWidget t m => m ()
106+
bodyElement = do
107+
rec evIncr <- button "+1"
108+
el "div" $ display count
109+
evDecr <- button "-1"
110+
count <- foldDyn (+) 0 $ leftmost [1 <$ evIncr, -1 <$ evDecr]
111+
return ()
112+
113+
main :: IO ()
114+
main = mainWidget bodyElement
115+
```
116+
117+
Let's take it line by line. In Reflex, we use `do` syntax to lay out the order of HTML elements.
118+
119+
```haskell
120+
bodyElement = do
121+
```
122+
123+
We first create a button `evIncr <- button "+1"` and bind its click event to the name `evIncr`. Then we create a `<div>` that displays the count, which is defined below. Then we create another button, naming its event `evDecr`.
124+
125+
```haskell
126+
rec evIncr <- button "+1"
127+
el "div" $ display count
128+
evDecr <- button "-1"
129+
```
130+
131+
Let's examine the final line from right-to-left:
132+
133+
```haskell
134+
count <- foldDyn (+) 0 $ leftmost [1 <$ evIncr, -1 <$ evDecr]
135+
```
136+
137+
`evIncr` and `evDecr` are of type `Event ()`, but we wish to have events of type `Event Int`, so we use `<$ :: a -> Event b -> Event a` in `[1 <$ evIncr, -1 <$ evDecr]`, to obtain a list of type `[Event Int]`. We merge this list with the leftmost function of type `[Event a] -> Event a`, which is thusly named because in the case when events occur simultaneously (which is impossible in this example because only one button can be clicked at a time) it chooses only the event leftmost in the list. To take stock, `leftmost [1 <$ evIncr, -1 <$ evDecr]` is of type `Event Int`, which you can think of as a list of all the mutations we want to take place on the `count`, which could look like `1 1 -1 1 1 -1 -1...`. At the end we wish to have a Dynamic value that represents the count at any point in time, thus we must sum up these events with `foldDyn :: (a -> b -> b) -> b -> Event a -> m (Dynamic b)`.
138+
139+
It's not as meaningful in a small example, but even here you can see how `count` is defined much more explicitly than in the Elm example. If we wish to understand how `count` behaves we have a singular place to look for what can affect it (`evIncr` and `evDecr`), and precisely how (mapping them to 1 and -1, respectively, and summing them up). We will see this benefit more clearly in a larger example.
140+
141+
Howver the architecture used here does not properly scale. For example, say we wanted to be able to set the value of the counter to a specific value, say another Dyanmic `dynNum1` in response to a third button. So instead of mapping our events to events of integers and summing them up, let's map our events to events of functions that take the previous value of the `count` and step it forward by that event.
142+
143+
```haskell
144+
count <- foldDyn ($) (0) $ leftmost
145+
[ (+ 1) <$ evIncr
146+
, (+ (-1)) <$ evDecr
147+
, dynNum1 <$ evSet
148+
]
149+
```
150+
151+
Here we partially apply `+` to one argument to create a one input function that adds either `1` or `-1` to the previous value. The other confusing bit of this new definition is the `($) :: (a -> b) -> a -> b` operator. It represents functional application, so here it simply applies the function to the previous value.
152+
153+
This pattern is similar to the Elm Architecture in that it is a reduction over various events. However we must note that this is a local reduction solely for this piece of state, and if there were other independent pieces of state in this application, they would have seperate reductions, which would possible reply on the same or other events. It is this clear independence between what is inherently independent that we need to be able to effectively determine what parts of a large codebase to read and which it ignore. Let's step up the size of the codebase under examination a bit to see why.
154+
155+
## 4. TodoMVC Comparison
156+
157+
ToDoMVC has become a standard application to compare front-end frameworks. It runs ~300 lines in both Elm and Reflex.
158+
159+
![image](https://user-images.githubusercontent.com/2288939/43979574-2cef3b36-9cb9-11e8-98f9-7cb25b27326a.png)
160+
161+
We compare [Elm ToDoMVC](https://github.com/evancz/elm-todomvc/blob/166e5f2afc704629ee6d03de00deac892dfaeed0/Todo.elm) with [Reflex ToDoMVC](https://github.com/reflex-frp/reflex-todomvc/blob/cd15a37b0e6decf42840967ce5fba6a03cf278fa/src/Reflex/TodoMVC.hs).
162+
163+
Say we wish to understand the behavior of the list of todo items.
164+
165+
### 4.1 Elm TodoMVC
166+
167+
In Elm, 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:
168+
169+
![image](https://user-images.githubusercontent.com/2288939/42886488-ab1c24c4-8a71-11e8-92f5-13dc2f282ad4.png)
170+
171+
Thus there is no single place to look to understand this list. We must ctl-F for all occurances of `entries =`.
172+
173+
![screenshot 2018-08-10 at 11 15 03 am](https://user-images.githubusercontent.com/2288939/43979683-95c27cfe-9cb9-11e8-805e-f2ab1e463f9f.png)
174+
175+
In other words, we cannot gain a complete understanding of any piece of state by looking in one place. We have to look through *all* messages to see if it affects the state in question, and then piece together *in our head* how the sum total of these effects come together to form an integrated behavior. In this way, the Elm Architecture simulates the "primitive word-at-a-time style of program-ming inherited from the von Neumann computer ... instead of encouraging us to think in terms of the larger conceptual units of the task at hand." [Can Programming Be Liberated...?]
176+
177+
There's an additional subtler way the Elm Architecture undermines explicitness: each piece of state can be modified in terms of *any other piece of state*. There's no explicit isolation between independent states.
178+
179+
Next, where do these messages come from? Unfortunately, any view element can emit any number of messages. We know from our ctl-F above that the `Add`, `EditingEntry`, `UpdateEntry`, `Delete`, `DeleteComplete`, `Check`, and `CheckAll` events can effect the `entries`, so now we must ctl-f for each of those events one by one to see which HTML elements emit those messages in reponse to which events. We'll just give you a small taste of how the viewInput's `onInput` and `onEnter` events send `UpdateField` and `Add` messages, respectively:
180+
181+
![image](https://user-images.githubusercontent.com/2288939/42886260-13e14bb6-8a71-11e8-8961-044c1a596b8a.png)
182+
183+
If we're looking to understand a single piece of state in Elm, 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. Explictness is lost as surely as if we passed around a compound state value to all of our functions, which is in fact what we've done.
184+
185+
### 4.2 Reflex ToDoMVC
186+
187+
By contrast, if we wish to understand the same piece of state in Reflex's TodoMVC, there is a single explicit place to look. As the code is dense and difficult to parse, I've taken the liberty to type annotate it:
62188

63189
```haskell
64190
todoMVC :: ( DomBuilder t m
@@ -73,18 +199,16 @@ todoMVC = do
73199
elAttr "section" ("class" =: "todoapp") $ do
74200
mainHeader
75201

76-
foldDyn :: (a -> b -> b ) -> b -> Event a -> m (Dynamic b)
77-
tasks :: Dynamic (Map Int Task)
78-
rec tasks <- foldDyn (\transformation oldTasks -> transformation oldTasks) initialTasks listTransformationEvent
79-
80-
mergeWith :: (a -> a -> a) -> [Event a] -> Event a
81-
listTransformationEvent :: Event (Map Int Task -> Map Int Task)
82-
let listTransformationEvent = mergeWith (.) [
83-
insertNewEvent
84-
, listModifyTasks
85-
, clearCompletedEvent
86-
]
202+
rec tasks :: Dynamic (Map Int Task)
203+
tasks <- foldDyn ($) initialTasks listTransformationEvent
87204

205+
let listTransformationEvent :: Event (Map Int Task -> Map Int Task)
206+
listTransformationEvent = leftmost
207+
[ insertNewEvent
208+
, listModifyTasks
209+
, clearCompletedEvent
210+
]
211+
88212
let insertNewEvent :: Event (Map Int Task -> Map Int Task)
89213
insertNewEvent = fmap insertNew_ newTask
90214
newTask <- taskEntry
@@ -96,29 +220,31 @@ todoMVC = do
96220
fmap :: (a -> b) -> Dynamic a -> Dynamic b
97221
let clearCompletedEvent :: Event (Map Int Task -> Map Int Task)
98222
clearCompletedEvent = fmap (\_ -> Map.filter $ not . taskCompleted) clearCompleted
223+
99224
return ()
225+
100226
infoFooter
101227
```
102228

103-
## 4. Is the cure worse than the disease?
229+
TODO explain each section of this piece by piece
230+
231+
## 5. Is the cure worse than the disease?
104232

105-
* how the elm architecure is good
233+
* how the elm architecure is good (LOGO / one piece at a time is easy to reason about from a writing perspective)
106234
* why higher order and cyclic streams are difficult
107235
* Also note the dependency on lazy eval which is part of the reason for space leakiness.
108236
* possible in JS without Haskell-y runtime?
109237

110-
## 5. Related Work
238+
## 6. Related Work
111239

112-
* program slicing (and the other one)
240+
The field of program slicing takes a different tatic to this problem. It leaves the underlying imperative programming model the same, and uses both static analysis and execution traces to determine which sections of code are relevant. [4]
113241

114-
## 6. Future work
242+
## 7. Future work
115243

116244
* Visualizations
117245
* Building a langauge that's easier to use - maybe in JS
118246

119-
## 7. Conclusions
120-
121-
TODO
247+
## 8. Conclusion
122248

123249

124250
<script>

0 commit comments

Comments
 (0)