Explicitly Comprehensible Functional Reactive Programming

Explicitly Comprehensible Functional Reactive Programming

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. Amore 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 { count : Int } 2 In imperative languages with global mutable state, variables can be 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 update msg model = 13 of them has become so large and contains irrelevant values that 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 [ text <| toString model.count ] 28 © 2018 Copyright held by the owner/author(s). , 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 Event () evIncr evIncr 33 to the names and , respectively. Notice that in Listing 2, line 11 count is used before it is defined. In main : Program Never Model Msg 34 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- , (\_ -> dynNum1) <$ evSet ues for all points in time. Reflex also has Dynamic values, which 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 write a global state reducer function that pattern matches on these 7 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.

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    5 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us