0% found this document useful (0 votes)
14 views5 pages

Comprehensible FRP

This paper discusses the challenges of comprehending Functional Reactive Programming (FRP) in The Elm Architecture and proposes using higher-order and cyclic streams from the Reflex library to enhance modularity and understanding. It contrasts the explicitness of state relationships in Reflex with the obfuscation present in Elm, particularly in state management and event handling. The author argues that while Reflex's approach improves clarity, it may introduce complexity in code density.

Uploaded by

balzofaye
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views5 pages

Comprehensible FRP

This paper discusses the challenges of comprehending Functional Reactive Programming (FRP) in The Elm Architecture and proposes using higher-order and cyclic streams from the Reflex library to enhance modularity and understanding. It contrasts the explicitness of state relationships in Reflex with the obfuscation present in Elm, particularly in state management and event handling. The author argues that while Reflex's approach improves clarity, it may introduce complexity in code density.

Uploaded by

balzofaye
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 5

Explicitly Comprehensible Functional Reactive Programming

Steven Krouse
[email protected]

ABSTRACT 2 THE ELM ARCHITECTURE


Functional Reactive programs written in The Elm Architecture are Elm is a pure functional language in the spirit of ML that compiles to
difficult to comprehend without reading every line of code. A more JavaScript. Its streams are first-order and non-cyclic, which means
modular architecture would allow programmers to understand a that streams cannot contain other streams, and streams cannot
small piece without reading the entire application. This paper shows reference streams that reference themselves, respectively. Let’s
how higher-order and cyclic streams, as demonstrated via the Reflex explore the architecture with a simple counter application.
library, can improve comprehensibility.

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

modified anywhere and in terms of any other global variables. In 3

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

the benefit of this knowledge as an aid to understanding is almost case msg of 14

nothing)." [7] Increment -> 15

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

rec evIncr <- button "+1" 10


3.1 Cyclic FRP
el "div" $ display count 11
Reflex’s higher-order and cyclic streams are necessary to maintain
evDecr <- button "-1" 12
explicitness in cyclical applications. "The DOM tree in an FRP ap-
count <- foldDyn (+) 0 $ leftmost 13
plication can change in response to changes in FRP behaviors and
[ 1 <$ evIncr 14
events. However, such new elements in the DOM tree may also
, -1 <$ evDecr 15 produce new primitive event streams that represent, for example,
] 16 button clicks on the event, which the FRP program needs to be able
return () 17 to react to. In other words, there is a cycle of dependencies..." [8].
18 Imagine a buttons that you can click to create more buttons, and
main :: IO () 19 when you click those buttons they also create buttons, and when
main = mainWidget bodyElement 20
you click those buttons... In Reflex, you’d accomplish this by first
describing a list of buttons of Dynamic length. This would emit a
Explicitly Comprehensible Functional Reactive Programming REBLS’18, November 2018, Boston, Massachusetts USA

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.

4.1 Elm TodoMVC


In Elm, any message can modify any state. For example, in update,
the Add message triggers an update of three different pieces of state:
Add ->
{ model
| uid = model.uid + 1
, field = ""
, entries = if String.isEmpty model.field then
Figure 2: Elm TodoMVC entries modifications highlighted
model.entries else model.entries ++ [ in reducer
newEntry model.field model.uid ]
}
Additionally, any view element can emit any number of mes-
Each piece of state can be modified in terms of any other piece of sages. We know from our Ctl-F above that the Add, EditingEntry
state. There’s no explicit isolation between independent states, so , UpdateEntry, Delete, DeleteComplete, Check, and CheckAll
it’s difficult to reason about the behavior of the application. This events can affect entries, so now we Ctl-F for each of those events
hinders modularity: components become difficult to disentangle to see which HTML elements emit those messages in response to
because the borders between them are blurred into a pooled state. which events as seen in Fig. 3.
Technically the Elm Architecture does allow for modular com- If we’re looking to understand a single piece of state in Elm, we’re
posibility, but it is discouraged. "Don’t reach for this initially as a not much better off than with an entirely imperative framework:
way to organise your application as ’components’. If you do this we still have to read more-or-less the whole application even if we
wish only to comprehend only a small piece. Modularity is lost as
1 https://github.com/evancz/elm-todomvc/blob/master/Todo.elm
2 https://github.com/reflex-frp/reflex-todomvc/blob/develop/src/Reflex/TodoMVC.hs 3 https://www.elm-tutorial.org/en-v01/02-elm-arch/08-composing-3.html
REBLS’18, November 2018, Boston, Massachusetts USA S. Krouse

Listing 3: Reflex TodoMVC


initialTasks :: Map Int Task 1

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

elAttr "section" ("class" =: "todoapp") $ do 14

mainHeader 15

rec tasks <- foldDyn ($) initialTasks $ 16

mergeWith (.)
[ fmap insertNew_ newTask 17

, listModifyTasks 18

, fmap (const $ Map.filter $ not . 19

taskCompleted) clearCompleted
] 20

newTask <- taskEntry 21

listModifyTasks <- taskList activeFilter 22


tasks
(activeFilter, clearCompleted) <- controls 23
tasks
return () 24
Figure 3: Elm TodoMVC relevant actions highlighted in view
infoFooter 25

Explicitness allows us to see the shape of this application, how


surely as if we passed around an arbitrarily-large compound state its pieces come together to make an integrated whole. We see where
value to all of our functions, which is in fact what we’ve done. code is independent, such as taskEntry, and dependent, such as
tasks on activeFilter. If we only cared about one specific piece
4.2 Reflex TodoMVC of an application, we could rely on these explicit relationships to
For contrast, if we wish to understand the same piece of state in determine which parts of the code are relevant (dependent) and
Reflex’s TodoMVC, there is a single explicit place to look, Listing which we can safely ignore (independent).
3, lines 16-19. This definition uses the more generalized pattern
discussed for the counter application above. There we had Event 5 IS THE CURE WORSE THAN THE DISEASE?
(Int -> Int) and here we have Event (Map Int Task -> Map This paper argues for higher-order and cyclic streams for the pur-
Int Task). Here tasks is defined as a merging of three event poses of comprehensibility. Yet the dense Reflex code isn’t easy
streams: to understand. The creator of Elm takes this stance, arguing that
(1) fmap insertNew_ newTask is where new tasks are added explicit dependencies lead to a "crazy" graph of dependencies4 .
to the list. The Elm Architecture has many benefits. For one, it simulates
(2) listModifyTasks handles the vast majority of task muta- global mutable state, which is very familiar to most programmers.
tions, including deletions, completions (and their reversal), The one-message-at-a-time style does simplify the code writing
and task text editing. This definition depends on tasks and process. It also reduces coupling between the view and the model,
activeFilter. making it easier to make changes. Finally, Elm’s model variable
(3) fmap (const $ Map.filter $ not . taskCompleted) is easily serialized, which allows for time-travel debugging, hot
clearCompleted filters out the currently-completed tasks reloading, and easy-to-implement undo features.
all at once when the bottom-right "Clear Completed" button While it is easier to write in the Elm Architecture, it is harder
is clicked. to navigate an large, unfamiliar codebase. Here Reflex shines by
4 https://youtu.be/DfLvDFxcAIA?t=27m32s
Explicitly Comprehensible Functional Reactive Programming REBLS’18, November 2018, Boston, Massachusetts USA

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

You might also like