You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).)
@@ -25,40 +25,166 @@ This paper contrasts The Elm Architecture with a state management pattern that m
25
25
26
26
## 2. The Elm Architecture
27
27
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.
29
29
30
30
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.
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:
35
35
36
-
```haskell
37
-
reducer::state->event->state
36
+
<buttononclick="num.innerText-=-1">+1</button>
37
+
<divid="num">0</div>
38
+
<buttononclick="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}
38
45
```
39
46
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/ReduxandElm 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.
41
48
42
-
Just like in imperitive programming, the ElmArchitecture is explict only about the *initial* values of states.A ny message can modify any state.In the reducer (called `update`in [ElmToDoMVC](https://github.com/evancz/elm-todomvc/blob/master/Todo.elm)), the `Add` message triggers an update of three different pieces of state:
The reducer steps the model forward in response to messages, thus simulating mutable variables that change over time.
45
56
46
-
You can't gain a complete understanding ofany piece of state by looking in one place.You have to look through *all* messages to see ifand how each affects the state in question.
57
+
```elm
58
+
type Msg
59
+
=Increment
60
+
|Decrement
47
61
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}
49
67
50
-
Additionally, any view element can emit any message.Again from [ElmToDoMVC](https://github.com/evancz/elm-todomvc/blob/master/Todo.elm), the viewInput's `onInput`and`onEnter` events send `UpdateField` and `Add` messages, respectively:
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.
53
73
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 evenif we wish only to comprehend only a small piece.
74
+
```elm
75
+
view:Model->HtmlMsg
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:ProgramNeverModelMsg
88
+
main =
89
+
Html.beginnerProgram
90
+
{ model = initialModel
91
+
, view = view
92
+
, update = update
93
+
}
94
+
```
55
95
56
96
## 3. Reflex
57
97
58
98
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.
59
99
60
-
*compareToDoMVC to Elm
61
-
* what does foldDyn doand 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:
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`.
`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.
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:
Thus there is no single place to look to understand this list. We must ctl-F for all occurances of `entries =`.
172
+
173
+

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:
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:
* how the elm architecure is good (LOGO / one piece at a time is easy to reason about from a writing perspective)
106
234
* why higher order and cyclic streams are difficult
107
235
* Also note the dependency on lazy eval which is part of the reason for space leakiness.
108
236
* possible in JS without Haskell-y runtime?
109
237
110
-
## 5. Related Work
238
+
## 6. Related Work
111
239
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]
113
241
114
-
## 6. Future work
242
+
## 7. Future work
115
243
116
244
* Visualizations
117
245
* Building a langauge that's easier to use - maybe in JS
0 commit comments