
Generic and Flexible Defaults for Verified, Law-Abiding Type-Class Instances Ryan G. Scott Ryan R. Newton Indiana University Indiana University United States United States [email protected] [email protected] Abstract (Haskell ’19), August 22–23, 2019, Berlin, Germany. ACM, New York, Dependently typed languages allow programmers to state NY, USA, 15 pages. https://doi.org/10.1145/3331545.3342591 and prove type class laws by simply encoding the laws as class methods. But writing implementations of these meth- 1 Introduction ods frequently give way to large amounts of routine, boil- Various programming languages support combining type erplate code, and depending on the law involved, the size classes [34], or similar features, with dependent type systems, of these proofs can grow superlinearly with the size of the including Agda [11], Clean [29], Coq [25], F* [19], Idris [7], datatypes involved. Isabelle [14], and Lean [5]. Even Haskell, the language which We present a technique for automating away large swaths inspired the development of type classes, is moving towards of this boilerplate by leveraging datatype-generic program- adding full-spectrum dependent types [12, 35], and deter- ming. We observe that any algebraic data type has an equiv- mined Haskell users can already write many dependently alent representation type that is composed of simpler, smaller typed programs using the singletons encoding [13]. types that are simpler to prove theorems over. By construct- Type classes and dependent types together make an ap- ing an isomorphism between a datatype and its represen- pealing combination since many classes come equipped with tation type, we derive proofs for the original datatype by algebraic laws that every class instance must obey. In a reusing the corresponding proof over the representation type. simply-typed setting, checking whether a given instance Our work is designed to be general-purpose and does not satisfies these laws is not straightforward, so these laws usu- require advanced automation techniques such as tactic sys- ally are presented as comments that the user must keep in tems. As evidence for this claim, we implement these ideas mind, informally, when writing code. For example, the fol- in a Haskell library that defines generic, canonical imple- lowing is an abridged version of Haskell’s Ord class along mentations of the methods and proof obligations for classes with one of its laws: in the standard base library. -- Transitivity: ifx ≤ y&&y ≤ z= True, CCS Concepts • Software and its engineering → Func- -- thenx ≤ z= True tional languages; Data types and structures. class Orda where (≤) :: a ! a ! Bool Keywords Type classes, generic programming, dependent In an ordinary Haskell setting, this important transitivity types, reuse law languishes as an unenforceable comment, and unless programmers are diligent, they may accidentally produce ACM Reference Format: Ord Ryan G. Scott and Ryan R. Newton. 2019. Generic and Flexible unlawful instances. With dependent types, however, the Defaults for Verified, Law-Abiding Type-Class Instances. In Pro- statement of this law can be encoded in the type signature ceedings of the 12th ACM SIGPLAN International Haskell Symposium of a class method, and anyone who defines an instance of the class must write a proof for it, ensuring that unlawful instances will be rejected from the get-go. For instance, one 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 could envision a version of Ord that bundles along the proof are not made or distributed for profit or commercial advantage and that of (≤)’s transitivity: copies bear this notice and the full citation on the first page. Copyrights class Orda where for components of this work owned by others than the author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, or (≤) :: a ! a ! Bool republish, to post on servers or to redistribute to lists, requires prior specific leqTransitive :: Π (x, y, z :: a) permission and/or a fee. Request permissions from [email protected]. ! (x ≤ y) :∼: True Haskell ’19, August 22–23, 2019, Berlin, Germany ! (y ≤ z) :∼: True © 2019 Copyright held by the owner/author(s). Publication rights licensed ! (x ≤ z) :∼: True to ACM. ACM ISBN 978-1-4503-6813-1/19/08...$15.00 Unfortunately, writing proofs in class instances can be la- https://doi.org/10.1145/3331545.3342591 borious, as many of these proofs require excessive amounts 15 Haskell ’19, August 22–23, 2019, Berlin, Germany Ryan G. Scott and Ryan R. Newton of boilerplate code. Even worse, the size of some proofs can classes in Haskell’s base library (previously asserted as un- grow super-linearly in the size of the datatypes used, as the verified comments). We additionally consider what an imple- length of the proofs can grow extremely quickly due to the mentation of these ideas would look like in other languages sheer number of cases one has to exhaust. in Section6. Many dependently typed languages automate the gener- ation of boilerplate code by providing tactics, as in Coq or 2 Background Lean. While heavy use of tactics can make swift work of Dependent types offer a vehicle for verifying type class laws many type-class proofs, they can sometimes make it tricky by simply defining additional class methods with proof obli- to update existing code, as basic changes to the code be- gations. In this section, we flesh out a complete example of ing verified can cause theorems that rely on the code’s im- a verified type class and demonstrate how one can define in- plementation details to suddenly fail. Moreover, not every stances for it using progressively larger datatypes. As the size dependently typed language is equipped with an advanced of the datatypes increases, it will become apparent that there tactic system. Porting tactic-rich proofs in one language to a is a problem of scale, since the number of cases required to different language that lacks them can be nontrivial, espe- complete the corresponding proofs increases quadratically. cially if the tactics automate away key steps in the proofs behind the scenes. 2.1 A Tour of Verified Classes Our work aims to introduce a general technique for elimi- The goal of our work is to present a technique that can be nating boilerplate code for type class laws that does not rely ported to any dependently typed programming language on tactics as the primary vehicle for proof automation. In- that supports type classes (a topic that we will revisit in stead, we adopt techniques from the field of datatype-generic Section6). In pursuit of this goal, in this paper we adopt programming—in particular, a dialect of generic program- the convention of using a minimalistic language that resem- ming that makes use of pattern functors [17, 20, 36]. We lever- bles Haskell. We say “resembles Haskell” rather than “is age pattern functors to decompose proofs into the smallest Haskell” since, in practice, our evaluation uses the single- algebraic components possible, compose larger proofs out of tons technique [13] to encode dependent types in Haskell. these verified building blocks, and then define instances for This process is quite straightforward, and we invite the inter- full datatypes by reusing the proofs over the pattern functor ested reader to Appendix A.1 [21] for the full details of how types. this works. However, the singletons encoding introduces a In fact, we can package up this style of code reuse into certain degree of syntactic noise, so for the sake of presen- generic default implementations of type class laws. These tation clarity, we use a syntax that is closer to most other defaults are flexible enough to work over any data type that dependently typed languages. (One can imagine that we are has an equivalent representation type built out of pattern writing code in a hypothetical Dependent Haskell [12].) functors. In general, there may be many ways to define an in- stance such that it abides by the class’s laws (see Section 3.3), 2.1.1 Classes but generic defaults à la pattern functors allow programmers As a running example, we use a minimal version of the Ord to abstract out canonical, “off-the-shelf” implementations type class, which describes datatypes that support boolean- that work over a wide variety of data types. These canonical returning comparison operations: defaults are highly useful—for instance, the n-body simu- lation in Vazou et al.[31] uses a canonical instance of the class Eqa ) Orda where Monoid class—so they receive the most attention in our work. (≤) :: a ! a ! Bool In particular, the primary contributions of this paper are: Where Eq and Bool are defined to be: • We show how to derive implementations of type-class class Eqa where data Bool = False | True methods and laws with the pattern functor approach (==) :: a ! a ! Bool to datatype-generic programming (Section3). In order for a datatype with an Ord instance to be considered • We provide the first datatype-generic approach to ver- verified, we require that the implementation of (≤) must ifying class laws that accommodates existing instances behave like a total order. That is to say, it must satisfy the (not written in any special style), while still providing following four laws: substantial proof automation (Section4). • We present a prototype implementation of these ideas Reflexivity x: x ≤ x and evaluate the lines of code required and impact on Transitivity 8x;y; z: x ≤ y ≤ z ) x ≤ z compile time (Section5). Antisymmetry 8x;y: x ≤ y ^ y ≤ x ) x = y Totality 8x;y: x ≤ y _ y ≤ x Our prototype implementation is a Haskell library, which 8 we call verified-classes.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages15 Page
-
File Size-