
Extending Monads with Pattern Matching Tomas Petricek Alan Mycroft Don Syme University of Cambridge Microsoft Research Cambridge ftomas.petricek, [email protected] [email protected] Abstract many monadic computations with an additional expressive power Sequencing of effectful computations can be neatly captured using that goes beyond sequencing. We presented an earlier form of joinads in F#[27]. This paper makes several novel findings, but monads and elegantly written using do notation. In practice such # monads often allow additional ways of composing computations, our first contribution to Haskell is similar to the earlier work in F : which have to be written explicitly using combinators. • We add language support for important kinds of computations, We identify joinads, an abstract notion of computation that is including parallel, concurrent and reactive programming. This stronger than monads and captures many such ad-hoc extensions. is done via a lightweight, reusable language extension that In particular, joinads are monads with three additional operations: builds on core functional concepts such as pattern matching. one of type m a ! m b ! m (a; b) captures various forms of parallel composition, one of type m a ! m a ! m a that is This paper simplifies the concept of joinad and requires that every inspired by choice and one of type m a ! m (m a) that captures joinad is also a monad (just like every group is also a monoid). aliasing of computations. Algebraically, the first two operations In Haskell, we also relate several ideas that already disconnectedly form a near-semiring with commutative multiplication. exist. The specific new contributions of this paper are: We introduce docase notation that can be viewed as a monadic • 1 version of case. Joinad laws imply various syntactic equivalences We present docase notation for Haskell (Sections 2, 4) that of programs written using docase that are analogous to equiva- allows programming with monadic computations extended with lences about case. Examples of joinads that benefit from the nota- aliasing, parallel composition and choice. We specify laws tion include speculative parallelism, waiting for a combination of about these operations to guarantee that docase keeps the user interface events, but also encoding of validation rules using the familiar semantics of pattern matching using case (Section 5). intersection of parsers. • To demonstrate the usefulness of the extension, we consider parsing (Section 3.1), GUI programming using events (Section Categories and Subject Descriptors D.3.3 [Language Constructs 3.2), lightweight concurrency (Section 3.4), and a parallelism and Features]: Control structures; F.1.2 [Models of Computation]: monad with support for speculative parallelism (Section 3.3). Parallelism and concurrency • The type of the above computations is captured by a Joinad General Terms Languages, Theory type class (Section 4.2). It relates type classes that have been already proposed for Haskell. Based on our experience, we pro- 1. Introduction pose and discuss several adjustments to the Haskell base library Monads are traditionally used for embedding sequential computa- and laws required by the type classes we combine (Section 8). tions into lazy functional code, but many recent uses go well be- • A joinad is an abstract computation that extends monads with yond sequencing of state or computations. Monads have been used three operations. Deriving the laws about the three operations for the exact opposite—to explicitly specify parallelism. This is (Section 6) reveals that two of the operations form an algebraic done by taking a core sequential monad and adding combinators structure known as a near-semiring. that increase the expressive power beyond sequencing. Monads for concurrent [5] and parallel programming [15] sup- The following section demonstrates the usefulness of docase in port forking and synchronizing computations [4]. A monad for the context of parallel programming. user-interface programming includes combinators for merging events from various sources [29]. These ad-hoc extensions are 2. Motivating example extremely useful, but they are not uniform. Developers have to Consider the following problem: we are given a tree with values in understand different combinators for every computation and they leaves and we want to test whether a predicate holds for all values lose the syntactic support provided by do notation. in the tree. This can be implemented as a recursive function: This paper discusses joinads—an abstract notion of computa- tions that extends monads. Joinads capture a pattern that appears in all :: (a ! Bool) ! Tree a ! Bool all p (Leaf v) = p v all p (Node left right) = all p left ^ all p right The execution of the two recursive calls could proceed in parallel. Moreover, when one of the branches completes returning False, it is not necessary to wait for the completion of the other branch as the overall result must be False. 1 Prototype version is available at http://github.com/tpetricek/ [Copyright notice will appear here once ’preprint’ option is removed.] Haskell.Joinads and we plan to submit a GHC patch in the future. 1 2011/7/11 Running two branches in parallel can be specified easily using instance MonadZip Parser where strategies [21, 32], but adding short-circuiting behaviour is chal- mzip (P p1 )(P p2 ) = P (λinp ! lenging. Using the docase notation and a monad for parallel pro- gramming, the problem can be solved as follows: [((a; b); num1 ; tail1 ) j (a; num1 ; tail1 ) p1 inp; all :: (a ! Bool) ! Tree a ! Par Bool (b; num2 ; tail2 ) p2 inp; num1 ≡ num2 ]) all p (Leaf v) = return (p v) all p (Node left right) = Figure 1. Instance of MonadZip for parsers docase (all p left; all p right) of (False; ?) ! return False (?; False) ! return False mzip :: Parser a ! Parser b ! Parser (a; b) (allL; allR) ! return (allL ^ allR) Judging by the type, the mzip operation could be implemented in The function builds a computation annotated with hints that specify terms of >>= and return. This implementation would not, in gen- how to evaluate it in parallel using the Par monad [15] extended eral, obey the laws we require. We give more details in Section 6.1. with the support for non-deterministic choice operator [26]. For parsers, the mzip operation parses the input using both parsers To process sub-trees in parallel, the snippet constructs two com- and then returns all combination of values such that the two parsers putations (of type Par Bool) and uses them as arguments of consumed the same number of input characters. The meaning of docase. Patterns in the alternatives correspond to individual com- this operation is that it creates a parser for a language that is an putations. A special pattern ? denotes that a value of the monadic intersection of languages described by the two parsers. computation does not have to be available for the alternative to be This implementation of mzip for parsers is shown in Figure 1. selected. When the processing of the left subtree completes and It applies the two parsing functions to the same input and then returns False, the first alternative can be selected immediately, be- returns all combinations for which the predicate num1 ≡ num2 cause the result of the second computation is not required. holds. An alternative implementation could compare the tails, but If the result of the left subtree is True and the right one has that would be inefficient and would not work for infinite inputs. not completed, none of the alternatives are immeriately enabled. The function belongs to the MonadZip type class that has been After the right subtree is processed, one of the last two alternatives added to GHC as part of a recent implementation of monad compre- can be selected. The choice operator added to the Par monad is hensions. Monad comprehensions [36] generalize list comprehen- non-deterministic, so the programmer needs to provide alternative sions to work with an arbitrary monad. The recent extension [1, 9] clauses that produce the same result in case of race. We return to also generalizes grouping and ordering [16] and syntax for zipping this topic in Section 3.3, but it is, the case in the above example. (zip comprehensions), hence the name mzip. To demonstrate the The selection between alternative clauses is done using the parallel between docase and generalized monad comprehensions, choice operator. Note that the result of each computation is used we start with an example written using both notations. in two independent alternatives. Evaluating the argument repeat- edly would defeat the purpose of docase, so the translation uses Example. Cambridge telephone numbers can be specified as the aliasing operator to avoid this. The third alternative combines strings satisfying three independent rules: they consist of 10 char- two computations, which is achieved using parallel composition acters, contain only digits and they start with the prefix 1223. The operator provided by the Par monad. following snippet shows how to encode this rule using both parallel The translation of docase is more complex than of the do monad comprehensions and the docase notation: notation. This is not a bad thing—the notation can be used to write programs that would otherwise be very complex. In the above valid = docase (many (sat isDigit); example, developers would typically write the solution in a more replicateM 10 item; imperative style shown in Appendix A. The length of the explicit startsWith (string "1223")) version is 21 lines compared to 6 line in the version above. of (num; ; ) ! return num valid = [num j num many (sat isDigit) 3. Introducing docase j replicateM 10 item This section introduces docase using four examples. We first j startsWith (string "1223")] consider docase expressions with a single alternative that can be The three arguments of the docase construct are combined using also written using zip comprehensions [9] and then gradually add the mzip function.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages12 Page
-
File Size-