Comprehensible FRP
Comprehensible FRP
Steven Krouse
[email protected]
CCS CONCEPTS
• Software and its engineering → Functional languages; Data
types and structures; Patterns; Frameworks;
KEYWORDS
functional reactive programming, elm, reflex
ACM Reference Format:
Steven Krouse. 2018. Explicitly Comprehensible Functional Reactive Pro- (a) The Elm Architecture (b) Simple Counter App
gramming. In Proceedings of SPLASH Boston (REBLS’18). ACM, New York,
NY, USA, 5 pages.
Listing 1: Elm Counter
1 INTRODUCTION type alias Model = 1
In imperative languages with global mutable state, variables can be { count : Int } 2
functional programming without mutable state, all terms explicitly initialModel : Model 4
list what they depend upon. This explicitness makes it easy to initialModel = 5
determine which parts of the code are dependent and independent { count = 0 } 6
of each other. 7
However, it is still possible to obfuscate the relationships between type Msg 8
pieces of state in functional programming. One can simulate global = Increment 9
mutable state by passing around an arbitrarily large compound state | Decrement 10
value as an extra parameter to each function. This is considered
11
an anti-pattern because "ease of reasoning is lost (we still know
update : Msg -> Model -> Model 12
that each function is dependent only upon its arguments, but one
of them has become so large and contains irrelevant values that update msg model = 13
Yet in client-side Functional Reactive Programming (FRP), a vari- { model | count = model.count + 1 } 16
ation on this anti-pattern has become the dominant architecture. Decrement -> 17
Originally conceived for the Elm programming language [2], The { model | count = model.count - 1 } 18
Elm Architecture has since inspired ReactJS’s Redux, VueJS’s Vuex, 19
CycleJS’s Onionify, among many other front-end state management view : Model -> Html Msg 20
libraries. This paper contrasts The Elm Architecture with a state view model = 21
management pattern in the Reflex library that maintains explicit
div [] 22
relationships.
[ button 23
Permission to make digital or hard copies of part or all of this work for personal or [ onClick Increment ] 24
classroom use is granted without fee provided that copies are not made or distributed [ text "+1" ] 25
for profit or commercial advantage and that copies bear this notice and the full citation
on the first page. Copyrights for third-party components of this work must be honored. , div 26
For all other uses, contact the owner/author(s). [] 27
REBLS’18, November 2018, Boston, Massachusetts USA
© 2018 Copyright held by the owner/author(s).
[ text <| toString model.count ] 28
, button 29
REBLS’18, November 2018, Boston, Massachusetts USA S. Krouse
[ onClick Decrement ] 30 Reflex uses monadic do syntax to lay out the order of HTML
[ text "-1" ] 31 elements [3]. In Listing 2, line 10 and 12, we create buttons with
] 32 text "+1" and "-1", and bind their click event streams of type
33
Event () to the names evIncr and evIncr, respectively. Notice
main : Program Never Model Msg 34
that in Listing 2, line 11 count is used before it is defined. In
Reflex, statements are arranged vertically in the order in which
main = 35
they appear in the HTML DOM tree. The recursive-do syntax [5]
Html.beginnerProgram 36
allow us to set up an event propagation network at the same time
{ model = initialModel 37
as we lay our our HTML elements. To calculate the count from the
, view = view 38
button click events, we use:
, update = update 39
• <$ (which is equivalent to Fran’s -=> operator [4]) to to map
} 40
each click event to either 1 or -1
The core of the architecture is its a compound state value, model, • leftmost to merge (left-biased for simultaneous events) the
Listing 1, lines 1-6. It represents the entirety of an applications two event streams into a single event stream, and
state at any given time. Just like in imperative programming, the • foldDyn (+) to sum them up.
Elm Architecture is explicit only about the initial values of the However this architecture does not properly generalize. For ex-
model, here defined in Listing 1, line 6. The reducer, Listing 1, ample, say we wanted to be able to set the value of the counter to
lines 12-18, steps the model forward in response to messages. a specific value, say another Dynamic, dynNum1, in response to a
Messages are generated from events in the view Listing 1, lines third button press. Instead of summing of Event Int, we can step
20-32, such as the Increment and Decrement messages, both from forward the previous value of state with Event (Int -> Int). In
onClick events. Haskell, the dollar-sign operator, ($):: (a -> b)-> a -> b, rep-
resents functional application, so here it applies each event function
3 REFLEX to the previous value of count.
The Reflex library was built for Haskell web development via ghcjs, count <- foldDyn ($) 0 $ leftmost
a Haskell to JavaScript compiler. Like in traditional FRP[4], Reflex
[ (+ 1) <$ evIncr
has two main concepts: Events and Behaviors. Events are discrete
, (+ (-1)) <$ evDecr
occurrences in time, while Behaviors are continuously defined val-
ues for all points in time. Reflex also has Dynamic values, which , (\_ -> dynNum1) <$ evSet
have the properties of both: they are defined at all points in time ]
and emit events at the discrete points in time when they change. This pattern is similar to the Elm Architecture in that it is a
In the following examples we use Events and Dynamics. reduction over events. However this is a local reduction solely for
this piece of state. If there were other independent pieces of state
Listing 2: Reflex Counter
in this application, they would have separate reductions.
button :: Text -> m (Event ()) 1
Even in this small example we can see how count is defined
el :: Text -> m a -> m a 2 more explicitly than in Elm. If we wish to understand how count
display :: Show a => Dynamic a -> m () 3 behaves, we have a singular place to look for what it depends upon,
(<$) :: a -> Event b -> Event a 4 evIncr and evDecr, and precisely how, mapping them to (+ 1)
leftmost :: [Event a] -> Event a 5 and (+ (-1)), respectively, and then merging and applying.
foldDyn :: (a -> b -> b) -> b -> Event a -> m ( 6 The price we pay for this explicitness is that events are abstracted
Dynamic b) from the single messages in Elm into streams of values. In Elm we
7
write a global state reducer function that pattern matches on these
event messages. In Reflex we use stream combinators to define the
bodyElement :: MonadWidget t m => m () 8
model and view as streams of each other.
bodyElement = do 9
Dynamic list of Events, a higher-order stream. If you flattened the too soon you will end up with plently of boilerplate." 3 . Thus we
the higher-order stream, counted all click Event occurrences, and can expect Elm applications to grow entangled and stay that way.
added 1 for the original button, you’d get the desired length of the Like in a language with global mutable state, there is no single
Dynamic list of buttons. Reflex would let you cyclically feed this place to look to understand how the todo items list, here called
Dynamic value back as the length of the list of buttons. entries, behaves. We must Ctl-F for all occurrences of entries
In Elm the cyclic nature of this application wouldn’t be apparent =, as seen in Fig. 2, and then piece together in our head how
in the code. You’d have a have a list of buttons of length count, a the sum total of these effects come together to form an integrated
value defined in the model. The value of count would increase in behavior. In this way, the Elm Architecture’s reducer simulates the
response to Increment messages, so you’d make sure that all your "primitive word-at-a-time style of programming inherited from the
buttons emit Increment messages in response to click events. The von Neumann computer ... instead of encouraging us to think in
issue with the Elm approach is that it’s too general: any view element terms of the larger conceptual units of the task at hand."[1]
could also issue Increment messages, and any other messages could
affect the count variable.
4 TODOMVC COMPARISON
Let’s compare Elm ToDoMVC 1 with Reflex ToDoMVC 2 . Say we
wish to understand the behavior of the list of todo items in both
implementations.
initialTasks = Map.empty 2
insertNew_ :: Task -> Map Int Task -> Map Int Task 4
todoMVC :: ( DomBuilder t m 6
, DomBuilderSpace m ~ GhcjsDomSpace 7
, MonadFix m 8
, MonadHold t m 9
) 10
=> m () 11
todoMVC = do 12
el "div" $ do 13
mainHeader 15
mergeWith (.)
[ fmap insertNew_ newTask 17
, listModifyTasks 18
taskCompleted) clearCompleted
] 20
showing how the pieces of state fit together. Reflex does this, in part, REFERENCES
by exposing the cyclic nature of the cyclic interfaces, instead of [1] John Backus. 1978. Can Programming Be Liberated from the Von Neumann Style?:
obfuscating them behind a global variable modifiable from anyplace. A Functional Style and Its Algebra of Programs. Commun. ACM 21, 8 (Aug. 1978),
613–641. https://doi.org/10.1145/359576.359579
While Reflex enables understanding states via a single place in [2] Evan Czaplicki and Stephen Chong. 2013. Asynchronous functional reactive
the code, Elm enables understanding messages via a single place programming for GUIs. In ACM SIGPLAN Notices, Vol. 48. ACM, 411–422.
[3] Conal Elliott. 2009. Push-pull functional reactive programming. In Haskell Sympo-
in the code. If one wanted to understand the impact of a Reflex sium. http://conal.net/papers/push-pull-frp
stream on downstream states, one would have to play the reverse [4] Conal Elliott and Paul Hudak. 1997. Functional reactive animation. In ACM
of the Ctl-F game that we played in Elm. While this duality might SIGPLAN Notices, Vol. 32. ACM, 263–273.
[5] Levent Erkök and John Launchbury. 2000. Recursive monadic bindings. In ACM
seem equivalent, Reflex’s style is more comprehensible because Sigplan Notices, Vol. 35. ACM, 174–185.
understanding the behaviors of states is much more important than [6] Leo A Meyerovich, Arjun Guha, Jacob Baskin, Gregory H Cooper, Michael Green-
understanding the effects of messages. berg, Aleks Bromfield, and Shriram Krishnamurthi. 2009. Flapjax: a programming
language for Ajax applications. In ACM SIGPLAN Notices, Vol. 44. ACM, 1–20.
To be fair, let’s not understate the difficulty of writing Reflex [7] Ben Moseley and Peter Marks. 2006. Out of the tar pit. Software Practice Advance-
code. It is hell on earth, grappling with its unwieldy types, double ment (SPA) 2006 (2006).
[8] Bob Reynders, Dominique Devriese, and Frank Piessens. 2017. Experience Re-
fmaping over streams, and waiting for ghcjs to compile. Reflex is port: Functional Reactive Programming and the DOM. In Companion to the first
a reasonably sound computational model that is in much need of a International Conference on the Art, Science and Engineering of Programming. ACM,
usability upgrade. 23.
[9] JK van der Plas. 2016. Slim: functional reactive user interface programming. Master’s
thesis.
6 RELATED WORK
Other Haskell FRP implementations, such as Threepenny.gui5 and
Sodium6 have imperative solutions to the cyclic dependency prob-
lem. Slim is a Haskell DSL for FRP cyclic streams "using (safe)
recursive definitions" [9].
FlapJax was the first JavaScript-based FRP implementation [6].
xstate is modern JavaScript library featuring higher-order streams.
It allows for cyclic definitions, defined imperatively 7 .
7 CONCLUSION
As the popularity of FRP frameworks continues, it’s increasingly
important to have a data model architecture that prioritizes the
comprehensibility and modularity of large programs. This paper
does not present a direct solution to this problem, but instead at-
tempts to sound the alarm that what we’re currently satisfied with,
The Elm Architecture, is not good enough. The Reflex library, with
its higher-order and cyclic streams, points in the right direction,
but we are still far from a complete solution to the problem of
comprehensible user interface construction.
8 ACKNOWLEDGEMENTS
Jonathan Edwards provided invaluable encouragement, suggestions,
and mentorship.
5 https://wiki.haskell.org/Threepenny-gui
6 https://github.com/SodiumFRP
7 https://github.com/staltz/xstream#-imitatetarget