Programming with Applicative-Like Expressions

Programming with Applicative-Like Expressions

Programming with Applicative-like expressions Jan Malakhovski and Sergei Soloviev∗ IRIT, University of Toulouse-3 and ITMO University 2015 - 2019 Abstract The fact that Applicative type class allows one to express simple parsers in a variable-less combinatorial style is well appreciated among Haskell programmers for its conceptual simplicity, ease of use, and usefulness for semi-automated code generation (metaprogramming). We notice that such Applicative computations can be interpreted as providing a mechanism to construct a data type with “ports” “pluggable” by subcomputations. We observe that it is this property that makes them so much more convenient in practice than the usual way of building the same computations using conventional composition. We distill this observation into a more general algebraic structure of (and/or technique for expressing) “Applicative-like” computations and demonstrate several other instances of this structure. Our interest in all of this comes from the fact that the aforementioned instances allow us to express arbitrary transformations between simple data types of a single constructor (similarly to how Applicative parsing allows to transform from streams of Chars to such data types) using a style that closely follows conventional Applicative computations, thus greatly simplifying (if not completely automating away) a lot of boiler-plate code present in many functional programs. Contents 1 Preliminaries 2 2 Introduction 2 3 Motivating examples 3 4 Problem definition 5 5 Deriving the technique 6 arXiv:1905.10728v1 [cs.PL] 26 May 2019 6 Applying the technique 8 7 Scott-encoded representation 10 8 General Case 13 9 Formal Account 13 9.1 Dependently-typedApplicative . ................. 14 10 Conclusion 16 ∗[email protected]; preferably with paper title in the subject line 1 1 Preliminaries This paper describes an algebraic structure (and/or a technique) for expressing certain kinds of computations. The presented derivation of said structure (technique) starts from observing Haskell’s Applicative type class [1] and then generalizing it. The result, however, is language-agnostic (the same way Applicative is, as both structures can be applied to most functional programming languages in some shape or form, even if a language in question can not explicitly express required type signatures) and theoretically interesting (as it points to several curious connections to category theory and logic). Since the idea grows from Haskell and most related literature uses Haskell, it is natural to express the derivation of the structure (technique) in Haskell. Therefore, this paper is organized as a series of Literate Haskell programs in a single Emacs Org-Mode tree [2, 3] (then, most likely, compiled into the representation you are looking at right now).1 Moreover, for uniformity reasons we shall also use Haskell type class names for the names of the corresponding algebraic structures where appropriate (e.g. “Applicative” instead of “applicative functor”) as not to cause any confusion between the code and the rest of the text. 2 Introduction Let us recall the definition of Applicative type class [1] as it is currently defined in the base [5] package of Hackage [6] infixl 4 <*> class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b One can think of the above definition as simply providing a generic “constant injector” pure and a somewhat generic “function application” (<*>) operator. (The referenced Functor type class and any related algebraic laws can be completely ignored for the purposes of this article.) For instance, an identity on Haskell types is obviously an Applicative with pure = id and (<*>) being the conventional function application (the one that is usually denoted by simple juxtaposition of terms), but there are many more complex instances of this type class (see [7, 8] for comprehensive overviews of this and related algebraic structures), most (for the purposes of this article) notably, including Applicative parsing combinators. Those are very popular in practice as they simplify parsing of simple data types (“simple” in this context means “without any type or data dependencies between different parts”) to the point of trivi- ality. For instance, given appropriate Applicative parsing machinery like Parsec [9], Attoparsec [10] or Megaparsec [11] one can parse a simple data type like data Device = Device { block :: Bool , major :: Int , minor :: Int } exampleDevice :: Device exampleDevice = Device False 19 1 from a straightforward serialized representation with just class Parsable a where parse :: Parser a 1 The source code is available at https://oxij.org/paper/ApplicativeLike/. It also gets embedded straight into the PDF version of the article when compiled with a modern TeX engine. Unfortunately, the file you are looking at was compiled using dvipdf. Properly compiled version is available via the above link. All runnable code in the paper was tested with GHC [4] version 8.6. 2 instance Parsable Device where parse = pure Device <*> parse <*> parse <*> parse While clearly limited to simple data types of a single2 constructor, this approach is very useful in practice. Firstly, since these kinds of expressions make no variable bindings and all they do is repeatedly apply parse it is virtually impossible to make a mistake. Secondly, for the same reason it is exceptionally easy to generate such expressions via Template Haskell and similar metaprogramming mechanisms. Which is why a plethora of Hackage libraries use this approach. In this paper we shall demonstrate a surprisingly simple technique that can be used to make computations expressing arbitrary transformations between simple data types of a single constructor while keeping the general form of Applicative expressions as they were shown above. Since we design our expressions to look similar to those produced with the help of Applicative type class but the underlying structure is not Applicative we shall call them “Applicative-like”. Section 3 provides some motivating examples that show why we want to use Applicative-like computations to express transformations between data types. Section 4 formalizes the notion of “Applicative-like” and discusses the properties we expect from such expressions. Section 5 derives one particular structure for one of the motivating examples using LISP-encoding for deconstructing data types. Section 6 proceeds to derive the rest of motivating examples by applying the same idea, thus showing that section 5 describes a technique, not an isolated example. Section 6 ends by demonstrating the total expressive power of the technique. Section 7 repeats the derivation and the implementations for Scott- encoded data types. Section 8 observes the general structure behind all of the terms used in the paper. Section 9 gives a formal description of the technique and the underlying general algebraic structure. Section 10 refers to related work and wraps everything up. 3 Motivating examples Consider the following expressions produced with the help of first author’s favorite safecopy [12] data-type-to-binary serialization-deserialization library which can be used to deserialize-serialize Device with the following code snippet (simplified3) instance SafeCopy Device where getCopy = pure Device <*> getCopy <*> getCopy <*> getCopy putCopy (Device b x y) = putCopy b >> putCopy x >> putCopy y Note that while getCopy definition above is trivial, putCopy definition binds variables. Would not it be better if we had an Applicative-like machinery with which we could rewrite putCopy into something like putCopy = depure unDevice <**> putCopy <**> putCopy <**> putCopy which, incidentally, would also allow us to generate both functions from a single expression? This idea does not feel like a big stretch of imagination for several reasons: • there are libraries that can do both parsing and pretty printing using a single expression, e.g. [13], • the general pattern of putCopy feels very similar to computations in (->) a (the type of “func- tions from a”) as it, too, is a kind of computation in a context with a constant value, aka Reader Monad [14], which is an instance of Applicative.4 2 Two or more constructors can be handled with the help of Alternative type class and some tagging of choices, but that is out of scope of this article. 3 The actual working code for the actual library looks a bit more complex, but the safecopy library also provides Template Haskell functions that derive these SafeCopy instances automatically, so, in practice, one would not need to write this code by hand in any case. 4 We shall utilize this fact in the following sections. 3 Another example is the data-type-to-JSON-to-strings serialization-deserialization part of aeson [15] library which gives the following class signatures to its deserializer and serializer from/to JSON respectively. class FromJSON a where parseJSON :: Value -> Parser a class ToJSON a where toJSON :: a -> Value In the above, Value is a JSON value and Parser a is a Scott-transformed variation of Either ErrorMessage a. Assuming (.:) to be a syntax sugar for lookup-in-a-map-by-name function and (.=) a pair constructor, we can give the following instances for the Device data type by emulating examples given in the package’s own documentation instance FromJSON Device where parseJSON (Object v) = pure Device <*> v .: "block" <*> v .: "major" <*> v .: "minor" parseJSON _ = empty instance ToJSON Device where toJSON (Device b x y) = object [ "block" .= b , "major" .= x , "minor" .= y

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    18 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