Causal Commutative Arrows Revisited
Total Page:16
File Type:pdf, Size:1020Kb
Causal Commutative Arrows Revisited Jeremy Yallop Hai Liu University of Cambridge, UK Intel Labs, USA [email protected] [email protected] Abstract init which construct terms of an overloaded type arr. Most of the Causal commutative arrows (CCA) extend arrows with additional code listings in this paper uses these combinators, which are more constructs and laws that make them suitable for modelling domains convenient for defining instances, in place of the notation; we refer such as functional reactive programming, differential equations and the reader to Paterson (2001) for the details of the desugaring. synchronous dataflow. Earlier work has revealed that a syntactic Unfortunately, speed does not always follow succinctness. Al- transformation of CCA computations into normal form can result in though arrows in poetry are a byword for swiftness, arrows in pro- significant performance improvements, sometimes increasing the grams can introduce significant overhead. Continuing with the ex- speed of programs by orders of magnitude. In this work we refor- ample above, in order to run exp, we must instantiate the abstract mulate the normalization as a type class instance and derive op- arrow with a concrete implementation, such as the causal stream timized observation functions via a specialization to stream trans- transformer SF (Liu et al. 2009) that forms the basis of signal func- formers to demonstrate that the same dramatic improvements can tions in the Yampa domain-specific language for functional reactive be achieved without leaving the language. programming (Hudak et al. 2003): newtype SF a b = SF {unSF :: a → (b, SF a b)} Categories and Subject Descriptors D.1.1 [Programming tech- niques]: Applicative (Functional) Programming (The accompanying instances for SF , which define the arrow operators, appear on page 6.) Keywords arrows, stream transformers, optimization, equational Instantiating exp with SF brings an unpleasant surprise: the reasoning, type classes program runs orders of magnitude slower than an equivalent pro- gram that does not use arrows. The programmer is faced with the 1. Introduction familiar need to choose between a high level of abstraction and acceptable performance. Liu et al. (2009) describe the problem in Arrows (Hughes 2000) provide a high-level interface to computa- more detail, and also propose a remedy: the laws which arrow im- tion, allowing programs to be expressed abstractly rather than con- plementations must obey can be used to rewrite arrow computations cretely, using reusable combinators in place of special-purpose con- into a normal form which eliminates the overhead of the arrow ab- trol flow code. Here is a program written using arrows: straction. Their design is formalized as a more restricted form of ar- exp = proc () → do rows, called causal commutative arrows (CCA), and implemented rec let e =1+ i as a Template Haskell library, which transforms the syntax of pro- i ← integral−≺ e grams during compilation to rewrite CCA computations into nor- returnA−≺ e mal form. The solution described by Liu et al. achieves significant perfor- which corresponds to the following recursive definition of the mance improvements, but the use of Template Haskell introduces exponential function a number of drawbacks. Perhaps most significantly, the Template t Haskell implementation of normalization is untyped: there is no e(t)=1+ e(t)dt Z check that the types of the unnormalized and normalized terms are 0 the same. Although the normalizer code operates on untyped syn- Paterson’s arrow notation (Paterson 2001), used in the definition of tax, its output is passed to the type checker, so there is no danger of exp, makes the data flow pleasingly clear: the integral function actually running ill-typed code. Nevertheless, the fact that the nor- forms the shaft of an arrow that turns e at the nock into i at malizer is not guaranteed to preserve typing means that errors may the head. The name e appears twice more, once above the arrow be discovered significantly later. A further drawback is that the nor- as the successor of i, and once below as the result of the whole malizer can only operate on computations whose structure is fully computation. (The definition of integral itself appears later in this known during compilation, when Template Haskell operates. paper, on page 4.) The notation need not be taken as primitive; there In this paper we address the first of these drawbacks and suggest is a desugaring into a set of combinators arr, ≫, first, loop, and a path to addressing the second. The reader familiar with recent Template Haskell developments might at this point expect us to propose switching the existing normalizer to using typed quotations and splices. Instead, we present a simpler approach, eschewing Permission to make digital or hard copies of part or all 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 syntactic transformations altogether and defining normalization as the full citation on the first page. Copyrights for components of this work owned by others than ACM must be honored. an operation on values, implemented as a type class instance. ThisAbstracting is the with author’s credit is permitted. version To of copy the otherwise, work. It to isrepublish, posted to herepost on for servers, your or topersonal redistribute use. to lists, Not contact for redistribution.the Owner/Author(s). The Request definitive permissions version from [email protected] was published in or Publications the following Dept., publication:ACM, Inc., fax +1 (212) 869-0481. 1.1 Contributions Haskell’16Haskell ’16 September, September 18–22, 2016, 22-23, Nara, Japan 2016, Nara, Japan Copyright c 2016 held by owner/author(s). Publication rights licensed to ACM. ACM.ACM 978-1-4503-4434-0/16/09. 978-1-4503-4434-0/16/09... $15.00 Section 2 reviews Causal Commutative Arrows (CCA), their defi- http://dx.doi.org/10.1145/2976002.2976019DOI: http://dx.doi.org/10.1145/2976002.2976019 nition as a set of Haskell type classes, the accompanying laws, and 21 class Arrow (arr :: ∗→∗→∗) where to applying arrow computation f to the first of a pair of inputs to obtain the first output, while connecting the second input directly to arr :: (a → b) → arr a b the second output. The dual of first, called second, can be defined ≫ ( ) :: arr a b → arr b c → arr a c as follows: first :: arr a b → arr (a, c)(b, c) second :: Arrow arr ⇒ arr a b → arr (c, a)(c, b) class Arrow arr ⇒ ArrowLoop arr where second f = arr swap ≫ first f ≫ arr swap loop :: arr (a, c)(b, c) → arr a b where swap (a, b)=(b, a) class ArrowLoop arr ⇒ ArrowInit arr where init :: a → arr a a Parallel composition ⋆⋆⋆ of two arrows can then be defined as a sequence of first and second: (⋆⋆⋆) :: Arrow arr ⇒ arr a b → arr c d → arr (a, c)(b, d) f ⋆⋆⋆ g = first f ≫ second g Together, these combinators form an interface to first-order com- (a) arr f (b) f ≫ g putations, i.e. computations which do not dynamically construct or change their compositional structure during the course of their ex- ecution. Like monads, all arrows are governed by a set of algebraic laws, which are shown in Figure 2a. (Lindley et al. (2010) further showed that these nine arrow laws can be reduced to eight.) It is worth not- (c) first f (d) loop f ing that all arrow laws respect the order of sequential composition of “impure” arrows, while a number of them (exchange, unit and Figure 1: The Arrow, ArrowLoop and ArrowInit classes association) allow “pure” arrows to be moved around without af- fecting the computation. Arrows can be extended to have more operations, governed by their normal form (CCNF). The contributions of the remainder of additional laws. Paterson (2001) defines the ArrowLoop class (Fig- this paper are as follows: ure 1) with an operator loop, which corresponds to the rec syntax in the arrow notation. Intuitively, the second output of the arrow in- • We derive a new implementation of CCA normalization, re- side loop is immediately connected back to its second input, and alised as a set of type class instances for a data type which thus becomes a form of recursion at value level, as opposed to represents the CCA normal form (Section 3) recursively defined arrows (an example of which is given in Sec- • We derive an optimized version of the “observation” function tion 5.3). Figure 2b gives the set of laws for ArrowLoop. that interprets normalized CCA computations using other arrow instances (Section 4). 2.2 Causal Commutative Arrows • We present a second implementation of the normalizing in- Based on looping arrows, Liu et al. (2009) introduces another stances for CCA based on mutable state, and use it to improve extension called causal commutative arrows (CCA) with an init the performance of the Euterpea library (Section 5). combinator in the ArrowInit class (Figure 1), and two additional laws to place further constraints on the computation (Figure 2c). In • We demonstrate via a series of micro- and macro-benchmarks the context of synchronous circuits, ArrowInit is almost identical that the performance of our normalizing instances compares to the ArrowCircuit class first introduced by Paterson (2001), favourably with the Template Haskell implementation used in with init being equivalent to delay that supplies its argument the original CCA work (Section 6). as its initial output, and copies from its input to the rest of its outputs. For the purpose of this paper, we will continue using the Finally, Section 7 contextualizes our work in the existing literature.