
Generics for the Masses Ralf Hinze Institut fur¨ Informatik III Universitat¨ Bonn Romerstraße¨ 164, 53117 Bonn, Germany [email protected] ABSTRACT it is simply not possible to define an equality test that works A generic function is a function that can be instantiated on for all types. Polymorphism does not help: equality is not many data types to obtain data type specific functionality. a polymorphic function since it must inspect its arguments. Examples of generic functions are the functions that can be Static typing dictates that equality becomes a family of func- derived in Haskell, such as show, read, and ‘ ’. The recent tions containing a tailor-made instance of equality for each years have seen a number of proposals that support the defi- type of interest. Rather annoyingly, all these instances have nition of generic functions. Some of the proposals define new to be programmed. languages, some define extensions to existing languages. As More than a decade ago the designers of Haskell noticed a common characteristic none of the proposals can be made and partially addressed this problem. By attaching a so- to work within Haskell 98: they all require something extra, called deriving form to a data type declaration the program- mer can instruct the compiler to generate an instance of either a more sophisticated type system or an additional 1 language construct. The purpose of this pearl is to show equality for the new type. In fact, the deriving mechanism that one can, in fact, program generically within Haskell 98 is not restricted to equality: parsers, pretty printers and obviating to some extent the need for fancy type systems several other functions are derivable, as well. These func- or separate tools. Haskell’s type classes are at the heart of tions have to become known as data-generic or polytypic this approach: they ensure that generic functions can be functions, functions that work for a whole family of types. defined succinctly and, in particular, that they can be used Unfortunately, Haskell’s deriving mechanism is closed: the painlessly. programmer cannot introduce new generic functions. The recent years have seen a number of proposals [9, 7, 2] that support exactly this, the definition of generic functions. Categories and Subject Descriptors Some of the proposals define new languages, some define ex- D.3.3 [Programming Languages]: Language Constructs tensions to existing languages. As a common characteristic and Features none of the proposals can be made to work within Haskell 98: they all require something extra, either a more sophisticated General Terms type system or an additional language construct. The purpose of this pearl is to show that one can, in fact, Languages program generically within Haskell 98 obviating to some ex- tent the need for fancy type systems or separate tools. The Keywords proposed approach is extremely light-weight; each imple- mentation of generics—we will introduce two major ones Generic programming, type classes, Haskell 98 and a few variations—consists roughly of two dozen lines of Haskell code. The reader is cordially invited to play with 1. INTRODUCTION the material. The source code can be found at A type system is like a suit of armour: it shields against the modern dangers of illegal instructions and memory vio- http://www.ralf-hinze.de/masses.tar.bz2 lations, but it also restricts flexibility. The lack of flexibility We have also included several exercises to support digestion is particularly vexing when it comes to implementing funda- of the material and to stimulate further experiments. mental operations such as showing a value or comparing two values. In a statically typed language such as Haskell 98 [11] 2. GENERIC FUNCTIONS ON TYPES This section discusses the first implementation of generics. Sections 2.1 and 2.2 introduce the approach from a user’s 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 perspective, Section 2.3 details the implementation, and Sec- not made or distributed for profit or commercial advantage and that copies 1 bear this notice and the full citation on the first page. To copy otherwise, to Actually, in Haskell 1.0 the compiler would always gener- republish, to post on servers or to redistribute to lists, requires prior specific ate an instance of equality. A deriving form was used to permission and/or a fee. restrict the instances generated to those mentioned in the ICFP’04, September 19–21, 2004, Snowbird, Utah, USA. form. To avoid the generation of instances altogether, the Copyright 2004 ACM 1-58113-905-5/04/0009 ...$5.00. programmer had to supply an empty deriving clause. tion 2.4 takes a look at various extensions, some obvious and a certain value. For the moment, it suffices to know that some perhaps less so. a type representation is simply an overloaded value called rep. The generic compression function is then given by the 2.1 Defining a generic function following simple, yet slightly mysterious definition. Let us tackle a concrete problem. Suppose we want to showBin :: (Rep α) ⇒ α → Bin encode elements of various data types as bit strings imple- showBin = appShowBin rep menting a simple form of data compression. For simplicity, we represent a bit string by a list of bits. Loosely speaking, we apply the generic function to the type representation rep. Of course, this is not the whole story. type Bin = [Bit ] The code above defines only a convenient shortcut. The data Bit = 0 | 1 deriving (Show) actual definition of showBin is provided by an instance dec- bits :: (Enum α) ⇒ Int → α → Bin laration, but you should read it instead as just a generic definition. We assume a function bits that encodes an element of an enumeration type using the specified number of bits. We instance Generic ShowBin where seek to generalize bits to a function showBin that works unit = ShowBin (λx → [ ]) for arbitrary types. Here is a simple interactive session that plus = ShowBin (λx → case x of Inl l → 0 : showBin l illustrates the use of showBin (note that characters consume Inr r → 1 : showBin r) 7 bits and integers 16 bits). pair = ShowBin (λx → showBin (outl x) ++ showBin (outr x)) Maini showBin 3 datatype iso [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] = ShowBin (λx → showBin (fromData iso x)) Maini showBin [3, 5] char = ShowBin (λx → bits 7 x) [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, int = ShowBin (λx → bits 16 x) 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] The class Generic has six member functions corresponding Maini showBin "Lisa" to the elementary types, Unit, Plus, and Pair, and to a small [1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, selection of primitive types, Char and Int. The member 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0] function datatype, which slightly breaks ranks, deals with A string of length n, for instance, is encoded in 8 ∗ n + 1 arbitrary data types. Each method binding defines the in- bits. stance of the generic function for the corresponding type. Implementing showBin so that it works for arbitrary data Let us consider each case in turn. To encode the single types seems like a hard nut to crack. Fortunately, generic element of the type Unit no bits are required (read: the in- programming comes to the rescue. The good news is that it stance of showBin for the Unit type is λx → [ ]). To encode suffices to define showBin for primitive types and for three an element of a sum type, we emit one bit for the construc- elementary types: the one-element type, the binary sum, tor followed by the encoding of its argument. The encoding and the binary product. of a pair is given by the concatenation of the component’s encodings. To encode an element of an arbitrary data type, data Unit = Unit we first convert the element into a sum of products, which is data Plus α β = Inl α | Inr β then encoded. Finally, characters and integers are encoded data Pair α β = Pair{outl :: α, outr :: β } using the function bits. That’s it, at least, as far as the generic function is con- Why these types? Well, Haskell’s construct for defining new cerned. Before we can actually compress data to strings of types, the data declaration, introduces a type that is iso- bits, we first have to turn the types of the to-be-compressed morphic to a sum of products. Thus, if we know how to values into representable types, which is what we will do compress sums and products, we can compress elements of next. an arbitrary data type. More generally, we can handle a type σ if we can handle some representation type τ that Exercise 1. Implement a generic version of Haskell’s com- is isomorphic to σ. The details of the representation type parison function compare :: (Rep α) ⇒ α → α → Ordering. are largely irrelevant. When programming a generic func- Follow the scheme above: first turn the signature into a tion it suffices to know the two mappings that witness the newtype declaration, then define compare, and finally pro- isomorphism. vide an instance of Generic. 2 Exercise 2. Implement a function readBin :: (Rep α) ⇒ data Iso α β = Iso{fromData :: β → α, toData :: α → β } Bin → α that decodes a bit string that was encoded by Turning to the implementation of showBin, we first have showBin.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages8 Page
-
File Size-