Sign up & Download
Sign in

Push-pull functional reactive programming

by Conal M Elliott
Proceedings of the 2nd ACM SIGPLAN symposium on Haskell Haskell 09 ()

Abstract

Functional reactive programming (FRP) has simple and powerful semantics, but has resisted efficient implementation. In particular, most past implementations have used demand-driven sampling, which accommodates FRP's continuous time semantics and fits well with the nature of functional programming. Consequently, values are wastefully recomputed even when inputs don't change, and reaction latency can be as high as the sampling period. This paper presents a way to implement FRP that combines data- and demand-driven evaluation, in which values are recomputed only when necessary, and reactions are nearly instantaneous. The implementation is rooted in a new simple formulation of FRP and its semantics and so is easy to understand and reason about. On the road to a new implementation, we'll meet some old friends (monoids, functors, applicative functors, monads, morphisms, and improving values) and make some new friends (functional future values, reactive normal form, and concurrent "unambiguous choice").

Cite this document (BETA)

Available from portal.acm.org
Page 1
hidden

Push-pull functional reactive pro...

Push-Pull Functional Reactive Programming Conal Elliott LambdaPix conal@conal.net Abstract Functional reactive programming (FRP) has simple and powerful semantics, but has resisted efficient implementation. In particular, most past implementations have used demand-driven sampling, which accommodates FRP���s continuous time semantics and fits well with the nature of functional programming. Consequently, values are wastefully recomputed even when inputs don���t change, and reaction latency can be as high as the sampling period. This paper presents a way to implement FRP that combines data- and demand-driven evaluation, in which values are recom- puted only when necessary, and reactions are nearly instantaneous. The implementation is rooted in a new simple formulation of FRP and its semantics and so is easy to understand and reason about. On the road to a new implementation, we���ll meet some old friends (monoids, functors, applicative functors, monads, mor- phisms, and improving values) and make some new friends (func- tional future values, reactive normal form, and concurrent ���unam- biguous choice���). Categories and Subject Descriptors D.1.1 [Software]: Program- ming Techniques���Applicative (Functional) Programming General Terms Design, Theory Keywords Functional reactive programming, semantics, concur- rency, data-driven, demand-driven 1. Introduction Functional reactive programming (FRP) supports elegant program- ming of dynamic and reactive systems by providing first-class, composable abstractions for behaviors (time-varying values) and events (streams of timed values) (Elliott 1996 Elliott and Hudak 1997 Nilsson et al. 2002).1 Behaviors can change continuously (not just frequently), with discretization introduced automatically during rendering. The choice of continuous time makes programs simpler and more composable than the customary (for computer programming) choice of discrete time, just as is the case with continuous space for modeled imagery. For instance, vector and 3D graphics representations are inherently scalable (resolution- independent), as compared to bitmaps (which are spatially dis- crete). Similarly, temporally or spatially infinite representations are 1 See http://haskell.org/haskellwiki/FRP for more references. Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, to republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Haskell���09, September 3, 2009, Edinburgh, Scotland, UK. Copyright c 2009 ACM 978-1-60558-508-6/09/09. . . $5.00 more composable than their finite counterparts, because they can be scaled arbitrarily in time or space, before being clipped to a finite time/space window. While FRP has simple, pure, and composable semantics, its ef- ficient implementation has not been so simple. In particular, past implementations have used demand-driven (pull) sampling of reac- tive behaviors, in contrast to the data-driven (push) evaluation typ- ically used for reactive systems, such as GUIs. There are at least two strong reasons for choosing pull over push for FRP: ��� Behaviors may change continuously, so the usual tactic of idling until the next input change (and then computing consequences) doesn���t apply. ��� Pull-based evaluation fits well with the common functional programming style of recursive traversal with parameters (time, in this case). Push-based evaluation appears at first to be an inherently imperative technique. Although some values change continuously, others change only at discrete moments (say in response to a button click or an object collision), while still others have periods of continuous change al- ternating with constancy. In all but the purely continuous case, pull- based implementations waste considerable resources, recomputing values even when they don���t change. In those situations, push-based implementations can operate much more efficiently, focusing com- putation on updating values that actually change. Another serious problem with the pull approach is that it im- poses significant latency. The delay between the occurrence of an event and the visible result of its reaction, can be as much as the polling period (and is on average half that period). In contrast, since push-based implementations are driven by event occurrences, reac- tions are visible nearly instantaneously. Is it possible to combine the benefits of push-based evaluation��� efficiency and minimal latency���with those of pull-based evaluation��� simplicity of functional implementation and applicability to tem- poral continuity? This paper demonstrates that it is indeed possible to get the best of both worlds, combining data- and demand-driven evaluation in a simple and natural way, with values being recom- puted only, and immediately, when their discrete or continuous inputs change. The implementation is rooted in a new simple for- mulation of FRP and its semantics and so is relatively easy to understand and reason about. This paper describes the following contributions: ��� A new notion of reactive values, which is a purely discrete sim- plification of FRP���s reactive behaviors (no continuous change). Reactive values have simple and precise denotational semantics (given below) and an efficient, data-driven implementation. ��� Decomposing the notion of reactive behaviors into independent discrete and continuous components, namely reactive values and (non-reactive) time functions. Recomposing these two no- tions and their implementations results in FRP���s reactive behav- iors, but now with an implementation that combines push-based
Page 2
hidden
and pull-based evaluation. Reactive values have a lazy, purely data representation, and so are cached automatically. This com- posite representation captures a new reactive normal form for FRP. ��� Modernizing the FRP interface, by restructuring much of its functionality and semantic definitions around standard type classes, as monoids, functors, applicative functors, and monads. This restructuring makes the interface more familiar, reduces the new interfaces to learn, and provides new expressive power. In most cases, the semantics are defined simply by choosing the semantic functions to be type class morphisms (Elliott 2009). ��� A notion of composable future values, which embody pure values that (in many cases) cannot yet be known, and is at the heart of this new formulation of reactivity. Nearly all the functionality of future values is provided via standard type classes, with semantics defined as class morphisms. ��� Use of Warren Burton���s ���improving values��� as a richly struc- tured (non-flat) type for time. Events, reactive values, reactive behaviors, and future values can all be parameterized with re- spect to time, which can be any ordered type. Using improving values (over an arbitrary ordered type) for time, the semantics of future values becomes a practical implementation. ��� A new technique for semantically determinate concurrency via an ���unambiguous choice��� operator, and use of this technique to provide a new implementation of improving values. 2. Functional reactive programming FRP revolves around two composable abstractions: events and be- haviors (Elliott and Hudak 1997). Because FRP is a functional paradigm, events and behaviors describe things that exist, rather than actions that have happened or are to happen (i.e., what is, not what does). Semantically, a (reactive) behavior is just a function of time, while an event (sometimes called an ���event source���) is a list of time/value pairs (���occurrences���). type Ba = T ��� a type Ea = [(b, T a)] -- for non-decreasing times Historically in FRP, T = R. As we���ll see, however, the semantics of behaviors assumes only that T is totally ordered. The type b T of occurrence times is T extended with -��� and ���. Orginally, FRP had a notion of events as a single value with time, which led to a somewhat awkward programming style with explicit temporal loops (tail recursions). The sequence-of-pairs for- mulation above, described in, e.g., (Elliott 1998a Peterson et al. 1999) and assumed throughout this paper, hides discrete time it- eration, just as behaviors hide continuous ���iteration���, resulting in simpler, more declarative specifications. The semantic domains Ba and Ea correspond to the behavior and event data types, via semantic functions: at :: Behavior a ��� Ba occs :: Event a ��� Ea This section focuses on the semantic models underlying FRP, which are intended for ease of understanding and formal reasoning. The insights gained are used in later sections to derive new correct and efficient representations. FRP���s Behavior and Event types came with a collection of combinators, many of which are instances of standard type classes. To dress FRP in modern attire, this paper uses standard classes and methods wherever possible in place of names from ���Classic FRP���. 2.1 Behaviors Perhaps the simplest behavior is time, corresponding to the identity function. time :: Behavior Time at time = id 2.1.1 Functor Functions can be ���lifted��� to apply to behaviors. Classic FRP (CFRP) had a family of lifting combinators: liftn :: (a1 ��� ... ��� an ��� b) ��� (Behavior a1 ��� ... ��� Behavior an ��� Behavior b) Lifting is pointwise and synchronous: the value of liftn f b1...bn at time t is the result of applying f to the values of the bi at (exactly) t.2 at (liftn f b1 ... bn) = ��t ��� f (b1 ���at��� t) ... (bn ���at��� t) The Functor instance for behaviors captures unary lifting, with fmap replacing FRP���s lift1. fmap :: (a ��� b) ��� Behavior a ��� Behavior b The semantic domain, functions, also form a functor: instance Functor ((���) t) where fmap f g = f ��� g The meaning of fmap on behaviors mimics fmap on the meaning of behaviors, following the principle of denotational design using type class morphisms (Elliott 2009) and captured in the following ���semantic instance���:3 instancesem Functor Behavior where at (fmap f b) = fmap f (at b) = f ��� at b In other words, at is a natural transformation, or ���functor mor- phism��� (for consistency with related terminology), from Behavior to B (Mac Lane 1998). The semantic instances in this paper (���instancesem ...���) specify the semantics, not implementation, of type class instances. 2.1.2 Applicative functor Applicative functors (AFs) are a recently explored notion (McBride and Paterson 2008). The AF interface has two methods, pure and ( * ), which correspond to the monadic operations return and ap. Applicative functors are more structured (less populated) than functors and less structured (more populated) than monads. infixl 4 * class Functor f ��� Applicative f where pure :: a ��� f a ( * ) :: f (a ��� b) ��� f a ��� f b These two combinators suffice to define liftA2, liftA3, etc. infixl 4 $ ( $ ) :: Functor f ��� (a ��� b) ��� f a ��� f b f $ a = fmap f a liftA2 :: Applicative f ��� (a ��� b ��� c) ��� f a ��� f b ��� f c liftA2 f a b = f $ a * b 2 Haskellism: The at function here is being used in both prefix form (on the left) and infix form (on the right). 3 Haskellism: Function application has higher (stronger) precedence than infix operators, so, e.g., f ��� at b ��� f ��� (at b).

Readership Statistics

53 Readers on Mendeley
by Discipline
 
 
 
by Academic Status
 
30% Other Professional
 
21% Student (Master)
 
19% Ph.D. Student
by Country
 
38% United States
 
15% United Kingdom
 
8% Russia

Tags

Sign up today - FREE

Mendeley saves you time finding and organizing research. Learn more

  • All your research in one place
  • Add and import papers easily
  • Access it anywhere, anytime

Start using Mendeley in seconds!

Already have an account? Sign in