15
Generic and Flexible Defaults for Verified, Law-Abiding Type-Class Instances Ryan G. Scott Indiana University United States [email protected] Ryan R. Newton Indiana University United States [email protected] Abstract Dependently typed languages allow programmers to state and prove type class laws by simply encoding the laws as class methods. But writing implementations of these meth- ods frequently give way to large amounts of routine, boil- erplate code, and depending on the law involved, the size of these proofs can grow superlinearly with the size of the datatypes involved. We present a technique for automating away large swaths of this boilerplate by leveraging datatype-generic program- ming. We observe that any algebraic data type has an equiv- alent representation type that is composed of simpler, smaller types that are simpler to prove theorems over. By construct- ing an isomorphism between a datatype and its represen- tation type, we derive proofs for the original datatype by reusing the corresponding proof over the representation type. Our work is designed to be general-purpose and does not require advanced automation techniques such as tactic sys- tems. As evidence for this claim, we implement these ideas in a Haskell library that defines generic, canonical imple- mentations of the methods and proof obligations for classes in the standard base library. CCS Concepts Software and its engineering Func- tional languages; Data types and structures. Keywords Type classes, generic programming, dependent types, reuse ACM Reference Format: Ryan G. Scott and Ryan R. Newton. 2019. Generic and Flexible Defaults for Verified, Law-Abiding Type-Class Instances. In Pro- ceedings of the 12th ACM SIGPLAN International Haskell Symposium 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 not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. Copyrights for components of this work owned by others than the author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Request permissions from [email protected]. Haskell ’19, August 22–23, 2019, Berlin, Germany © 2019 Copyright held by the owner/author(s). Publication rights licensed to ACM. ACM ISBN 978-1-4503-6813-1/19/08. . . $15.00 hps://doi.org/10.1145/3331545.3342591 (Haskell ’19), August 22–23, 2019, Berlin, Germany. ACM, New York, NY, USA, 15 pages. hps://doi.org/10.1145/3331545.3342591 1 Introduction Various programming languages support combining type classes [34], or similar features, with dependent type systems, including Agda [11], Clean [29], Coq [25], F* [19], Idris [7], Isabelle [14], and Lean [5]. Even Haskell, the language which inspired the development of type classes, is moving towards adding full-spectrum dependent types [12, 35], and deter- mined Haskell users can already write many dependently typed programs using the singletons encoding [13]. Type classes and dependent types together make an ap- pealing combination since many classes come equipped with algebraic laws that every class instance must obey. In a simply-typed setting, checking whether a given instance satisfies these laws is not straightforward, so these laws usu- ally are presented as comments that the user must keep in mind, informally, when writing code. For example, the fol- lowing is an abridged version of Haskell’s Ord class along with one of its laws: -- Transitivity: if x y && y z = True, -- then x z = True class Ord a where () :: a a Bool In an ordinary Haskell setting, this important transitivity law languishes as an unenforceable comment, and unless programmers are diligent, they may accidentally produce unlawful Ord instances. With dependent types, however, the statement of this law can be encoded in the type signature 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 could envision a version of Ord that bundles along the proof of ()’s transitivity: class Ord a where () :: a a Bool leqTransitive :: Π (x, y, z :: a) (x y) :: True (y z) :: True (x z) :: True Unfortunately, writing proofs in class instances can be la- borious, as many of these proofs require excessive amounts 15

Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for VerifiedLaw-Abiding Type-Class InstancesRyan G ScottIndiana University

United Statesrgscottindianaedu

Ryan R NewtonIndiana University

United Statesrrnewtonindianaedu

AbstractDependently typed languages allow programmers to stateand prove type class laws by simply encoding the laws asclass methods But writing implementations of these meth-ods frequently give way to large amounts of routine boil-erplate code and depending on the law involved the sizeof these proofs can grow superlinearly with the size of thedatatypes involved

We present a technique for automating away large swathsof this boilerplate by leveraging datatype-generic program-ming We observe that any algebraic data type has an equiv-alent representation type that is composed of simpler smallertypes that are simpler to prove theorems over By construct-ing an isomorphism between a datatype and its represen-tation type we derive proofs for the original datatype byreusing the corresponding proof over the representation typeOur work is designed to be general-purpose and does notrequire advanced automation techniques such as tactic sys-tems As evidence for this claim we implement these ideasin a Haskell library that defines generic canonical imple-mentations of the methods and proof obligations for classesin the standard base library

CCS Concepts bull Software and its engineeringrarr Func-tional languages Data types and structures

Keywords Type classes generic programming dependenttypes reuse

ACM Reference FormatRyan G Scott and Ryan R Newton 2019 Generic and FlexibleDefaults for Verified Law-Abiding Type-Class Instances In Pro-ceedings of the 12th ACM SIGPLAN International Haskell Symposium

Permission to make digital or hard copies of all or part of this work forpersonal or classroom use is granted without fee provided that copiesare not made or distributed for profit or commercial advantage and thatcopies bear this notice and the full citation on the first page Copyrightsfor components of this work owned by others than the author(s) mustbe honored Abstracting with credit is permitted To copy otherwise orrepublish to post on servers or to redistribute to lists requires prior specificpermission andor a fee Request permissions from permissionsacmorgHaskell rsquo19 August 22ndash23 2019 Berlin Germanycopy 2019 Copyright held by the ownerauthor(s) Publication rights licensedto ACMACM ISBN 978-1-4503-6813-11908 $1500httpsdoiorg10114533315453342591

(Haskell rsquo19) August 22ndash23 2019 Berlin Germany ACM New YorkNY USA 15 pages httpsdoiorg10114533315453342591

1 IntroductionVarious programming languages support combining typeclasses [34] or similar features with dependent type systemsincluding Agda [11] Clean [29] Coq [25] F [19] Idris [7]Isabelle [14] and Lean [5] Even Haskell the language whichinspired the development of type classes is moving towardsadding full-spectrum dependent types [12 35] and deter-mined Haskell users can already write many dependentlytyped programs using the singletons encoding [13]Type classes and dependent types together make an ap-

pealing combination since many classes come equipped withalgebraic laws that every class instance must obey In asimply-typed setting checking whether a given instancesatisfies these laws is not straightforward so these laws usu-ally are presented as comments that the user must keep inmind informally when writing code For example the fol-lowing is an abridged version of Haskellrsquos Ord class alongwith one of its laws

-- Transitivity if x le y ampamp y le z = True

-- then x le z = True

class Ord a where

(le) a rarr a rarr Bool

In an ordinary Haskell setting this important transitivitylaw languishes as an unenforceable comment and unlessprogrammers are diligent they may accidentally produceunlawful Ord instances With dependent types however thestatement of this law can be encoded in the type signatureof a class method and anyone who defines an instance ofthe class must write a proof for it ensuring that unlawfulinstances will be rejected from the get-go For instance onecould envision a version of Ord that bundles along the proofof (le)rsquos transitivity

class Ord a where

(le) a rarr a rarr Bool

leqTransitive Π (x y z a)

rarr (x le y) sim True

rarr (y le z) sim True

rarr (x le z) sim True

Unfortunately writing proofs in class instances can be la-borious as many of these proofs require excessive amounts

15

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

of boilerplate code Even worse the size of some proofs cangrow super-linearly in the size of the datatypes used as thelength of the proofs can grow extremely quickly due to thesheer number of cases one has to exhaustMany dependently typed languages automate the gener-

ation of boilerplate code by providing tactics as in Coq orLean While heavy use of tactics can make swift work ofmany type-class proofs they can sometimes make it trickyto update existing code as basic changes to the code be-ing verified can cause theorems that rely on the codersquos im-plementation details to suddenly fail Moreover not everydependently typed language is equipped with an advancedtactic system Porting tactic-rich proofs in one language to adifferent language that lacks them can be nontrivial espe-cially if the tactics automate away key steps in the proofsbehind the scenes

Our work aims to introduce a general technique for elimi-nating boilerplate code for type class laws that does not relyon tactics as the primary vehicle for proof automation In-stead we adopt techniques from the field of datatype-genericprogrammingmdashin particular a dialect of generic program-ming that makes use of pattern functors [17 20 36] We lever-age pattern functors to decompose proofs into the smallestalgebraic components possible compose larger proofs out ofthese verified building blocks and then define instances forfull datatypes by reusing the proofs over the pattern functortypesIn fact we can package up this style of code reuse into

generic default implementations of type class laws Thesedefaults are flexible enough to work over any data type thathas an equivalent representation type built out of patternfunctors In general there may be many ways to define an in-stance such that it abides by the classrsquos laws (see Section 33)but generic defaults agrave la pattern functors allow programmersto abstract out canonical ldquooff-the-shelfrdquo implementationsthat work over a wide variety of data types These canonicaldefaults are highly usefulmdashfor instance the n-body simu-lation in Vazou et al [31] uses a canonical instance of theMonoid classmdashso they receive the most attention in our workIn particular the primary contributions of this paper are

bull We show how to derive implementations of type-classmethods and laws with the pattern functor approachto datatype-generic programming (Section 3)bull We provide the first datatype-generic approach to ver-ifying class laws that accommodates existing instances(not written in any special style) while still providingsubstantial proof automation (Section 4)bull We present a prototype implementation of these ideasand evaluate the lines of code required and impact oncompile time (Section 5)

Our prototype implementation is a Haskell library whichwe call verified-classes The verified-classes libraryprovides generic implementations of laws for several type

classes in Haskellrsquos base library (previously asserted as un-verified comments) We additionally consider what an imple-mentation of these ideas would look like in other languagesin Section 6

2 BackgroundDependent types offer a vehicle for verifying type class lawsby simply defining additional class methods with proof obli-gations In this section we flesh out a complete example ofa verified type class and demonstrate how one can define in-stances for it using progressively larger datatypes As the sizeof the datatypes increases it will become apparent that thereis a problem of scale since the number of cases required tocomplete the corresponding proofs increases quadratically

21 A Tour of Verified ClassesThe goal of our work is to present a technique that can beported to any dependently typed programming languagethat supports type classes (a topic that we will revisit inSection 6) In pursuit of this goal in this paper we adoptthe convention of using a minimalistic language that resem-bles Haskell We say ldquoresembles Haskellrdquo rather than ldquoisHaskellrdquo since in practice our evaluation uses the single-tons technique [13] to encode dependent types in HaskellThis process is quite straightforward and we invite the inter-ested reader to Appendix A1 [21] for the full details of howthis works However the singletons encoding introduces acertain degree of syntactic noise so for the sake of presen-tation clarity we use a syntax that is closer to most otherdependently typed languages (One can imagine that we arewriting code in a hypothetical Dependent Haskell [12])

211 ClassesAs a running example we use a minimal version of the Ordtype class which describes datatypes that support boolean-returning comparison operations

class Eq a rArr Ord a where

(le) a rarr a rarr Bool

Where Eq and Bool are defined to be

class Eq a where data Bool = False | True

(==) a rarr a rarr Bool

In order for a datatype with an Ord instance to be consideredverified we require that the implementation of (le) mustbehave like a total order That is to say it must satisfy thefollowing four laws

Reflexivity forallx x le xTransitivity forallx y z x le y le z rArr x le z

Antisymmetry forallx y x le y and y le x rArr x = yTotality forallx y x le y or y le x

Using dependent types we encode these laws as proof meth-ods of a type class We define a new class VOrd (short for

16

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

ldquoverified Ordrdquo) to represent the group of Ord instances thatare known to be lawful total orders1

class Ord a rArr VOrd a where

leqReflexive Π (x a) rarr (x le x) sim True

leqTransitive

Π (x y z a)

rarr (x le y) sim True rarr (y le z) sim True

rarr (x le z) sim True

leqAntisymmetric

Π (x y a)

rarr (x le y) sim True rarr (y le x) sim True

rarr (x == y) sim True

leqTotal

Π (x y a)

rarr Either ((x le y) sim True)

((y le x) sim True (x == y) sim False)

The methods of VOrd translate the laws above into typesignatures making use of the propositional equality type(sim) which reflects the judgment that two types are equalinto a type of its own

data (sim) forall k k rarr k rarr Type where

Refl a sim a

For the sake of making these type signatures a little morebrief we introduce an auxiliary definition that reflects True-valued boolean judgments into types

data So Bool rarr Type where

Oh So True

Using So we can tighten up the presentation of VOrd some-what

class Ord a rArr VOrd a where

leqReflexive Π (x a) rarr So (x le x)

leqTransitive

Π (x y z a)

rarr So (x le y) rarr So (y le z) rarr So (x le z)

leqAntisymmetric

Π (x y a)

rarr So (x le y) rarr So (y le x) rarr So (x == y)

leqTotal

Π (x y a)

rarr Either (So (x le y))

(So (y le x) So (not (x == y)))

212 Examples of InstancesTo demonstrate VOrd in action we first define an extremelysimple datatype along with accompanying instances of Eqand Ord

1Alternatively we could inline the methods of VOrd directly into Ord Wechoose not to do so here since many languages such as Haskell make useof certain unlawful instances such as the Ord instances for Floats andDoubles

data T a = MkT1 a

instance Eq a rArr Eq (T a) where

(MkT1 x) == (MkT1 y) = (x == y)

instance Ord a rArr Ord (T a) where

(MkT1 x) le (MkT1 y) = x le y

Unsurprisingly this implementation of (le) comprises a totalorder We verify this claim by implementing a VOrd instancefor T like so

instance VOrd a rArr VOrd (T a) where

leqReflexive (MkT1 x)

| Oh larr leqReflexive x = Oh

leqTransitive (MkT1 x) (MkT1 y) (MkT1 z) Oh Oh

| Oh larr leqTransitive x y z Oh Oh = Oh

leqAntisymmetric (MkT1 x) (MkT1 y) Oh Oh

| Oh larr leqAntisymmetric x y Oh Oh = Oh

leqTotal (MkT1 x) (MkT1 y) =

case leqTotal x y of

Left Oh rarr Left Oh

Right (Oh Oh) rarr Right (Oh Oh)

The implementations of these proofs outline the basic ap-proach to verifying algebraic properties For each propertywe wish to prove we proceed by cases on T (encoded us-ing pattern matching) appeal to our induction hypothesis(encoded using recursion) and then complete the proof

This instance was relatively painless to write as therewas only one case to consider the MkT1 constructor of THowever the size of the proofs grows noticeably when weincrease the size of T For instance consider what happenswhen we add another constructor

data T a = MkT1 a | MkT2 a

First we must adjust our Eq and Ord instances accordinglyWe pick the convention that MkT1 is always less than MkT2

instance Eq a rArr Eq (T a) where

(MkT1 x) == (MkT1 y) = (x == y)

(MkT2 x) == (MkT2 y) = (x == y)

(MkT1 _) == (MkT2 _) = False

(MkT2 _) == (MkT1 _) = False

instance Ord a rArr Ord (T a) where

(MkT1 x) le (MkT1 y) = (x le y)

(MkT2 x) le (MkT2 y) = (x le y)

(MkT1 _) le (MkT2 _) = True

(MkT2 _) le (MkT1 _) = False

Next now that the code that wersquore verifying has changedwe must synchronize the accompanying proofs in the VOrdinstance We start with the proof of reflexivity

instance VOrd a rArr VOrd (T a) where

leqReflexive (MkT1 x)

| Oh larr leqReflexive x = Oh

leqReflexive (MkT2 x)

| Oh larr leqReflexive x = Oh

17

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Updating leqReflexive ended up not requiring that mucheffort as we only needed to have one extra case for the addi-tional constructor MkT2 Updating leqTransitive howeverrequires more effort

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

In addition to requiring an extra case for the combination ofarguments where all of them are MkT2 we now require anadditional case to cover the two combinations of argumentswhere the first argument is MkT1 and the third argument isMkT2 The four other possible combinations of argumentswere ruled out by dependent pattern matching as the typesystem concluded that they were impossible-to-reach cases(for example if t = MkT2 _ and t = MkT1 _ then it can-not be the case that t le t is True) In Haskell we do notneed to write these cases out at all although some otherlanguages may require explicit ldquoabsurdrdquo cases (eg using the() pattern in Agda)

Having to add one more case might not seem that bur-densome but the number of cases one has to supply forleqTransitive grows quickly as we add more and moreconstructors For example if we added a third constructorMkT3 to T then after updating the Eq and Ord instances (us-ing the convention that MkT1 is always less than MkT2 andMkT2 is always less than MkT3) this is what the proof oftransitivity would become

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT3 x MkT3 y MkT3 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

(MkT1 _ _ MkT3 _) = Oh

(MkT2 _ _ MkT3 _) = Oh

This time we have six cases If we add a fourth constructorwe would have 10 cases and if we add a fifth constructor wewould have fifteen cases In general if we had n constructorsthen leqTransitivewould require n+

(n2)= 1

2 (n2+n) cases

class Generic a where

type Rep a Type

from a rarr Rep a

to Rep a rarr a

data U1 = MkU1

newtype K1 c = MkK1 c

data a b = a b

data a + b = L1 a | R1 b

Figure 1A slightly simplified presentation of the definitionsfrom Haskellrsquos GHCGenerics library on which we base ourapproach to datatype-generic programming

which grows quadratically This has the potential to makescaling up proofs extremely tiresomePerhaps even more troublesome than the size of these

proofs themselves is the fact that most of these cases aresheer boilerplate For instance leqTransitive follows avery predictable pattern For combinations of argumentswhere all the constructors are the same recurse and forcombinations where there are different constructors it mustbe the case that the first argument is less than or equal to thethird argument so immediately return Oh This is routinecode that is begging to be automated with a proof-reusetechnique

3 Verified Instances GenericallyHaving seen the tedium of manually constructing certainproofs we present a solution Notably our solution does notrequire a tremendous amount of support from the languageitself (in terms of tactics or metaprogramming) Instead werely on techniques that could be ported to any dependentlytyped programming language that at a minimum supportstype classes2

We adapt an approach from the field of datatype-genericprogramming where we take an algebraic datatype and con-struct a representation type which is isomorphic to it Therepresentation type itself is a composition of smaller patternfunctor datatypes that encode primitive ldquobuilding blocksrdquo ofother datatypes such as products sums and individual fieldsWe also establish a type class for witnessing the isomorphismbetween a datatype and its representation type [17 20 36]By leveraging these tools we shift the burden of proof

from the original datatype (which may be arbitrarily com-plex) to the handful of simple pattern functor types thatconstitute its representation type This way we are able toprove properties for a wide range of possible datatypes bysimply proving the same properties for a finite number ofldquobuilding blockrdquo types

31 A Primer on Datatype-Generic ProgrammingTo build up representation types we build upon the tech-niques from Magalhatildees et al [17] which influenced the de-sign of Haskellrsquos popular GHCGenerics library Figure 12It is possible to further reduce the amount of code needed by generat-ing boilerplate code with metaprogramming techniques (eg TemplateHaskell [24]) but support for metaprogramming is not a strict requirement

18

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

presents the central definitions used in this style of datatype-generic programmingThe Generic type class is the focal point of the library

A datatype with a Generic instance comes equipped witha Rep the representation type and functions from and towhich witness the isomorphism between the representationtype and the original datatype The Rep type is always somecombination of the following pattern functor types3

1 The U1 type represents constructors with no fields2 The K1 type represents a single field in a constructor3 The () type (pronounced ldquoproductrdquo) represents

two consecutive fields in a constructor4 The (+) type (pronounced ldquosumrdquo) represents the

choice between two consecutive constructorsThis is best explained with an example so recall this ver-

sion of the T data type from Section 212

data T a = MkT1 a | MkT2 a

We define its canonical Generic instance like soinstance Generic (T a) where

type Rep (T a) = K1 a + K1 a

from (MkT1 x) = L1 (MkK1 x)

from (MkT2 x) = R1 (MkK1 x)

to (L1 (MkK1 x)) = MkT1 x

to (R1 (MkK1 x)) = MkT2 x

Here we see that because T has two constructors MkT1 andMkT2 the (+) type is used once to represent the choicebetween them The field of type a in each constructor is inturn represented with a K1 type The same sort of patternwould follow if we added more constructors to T For exam-ple here is how the Rep instance would appear if there werethree constructors 4

data T a = MkT1 a | MkT2 a | MkT3 Bool

instance Generic (T a) where

type Rep (T a) = K1 a + (K1 a + K1 Bool)

Each constructor corresponds to another use of (+) todenote another choice of constructor Despite the size of theT type increasing the number of distinct datatypes in its Rephas not changed which is an important property

The implementations of from and to are are entirely me-chanical to implement and constitute one of the few sourcesof boilerplate in this style of datatype-generic programmingLanguages like Haskell offer a metaprogramming facility(deriving Generic) for generating this boilerplate althoughit can just as well be written by hand3There is another pattern functor type M1 which is used to attach metadatasuch as constructor names fixities etc For the sake of simplicity we leaveit out of the discussion in this section but we revisit it in 514Although (+) is right-associative we have added explicit parenthesesin this Rep instance for clarity In general a canonical Generic instancebalances nested uses of (+) and () so that it is always possible togo from the root of the representation type to a leaf in logarithmic (ratherthan linear) time

32 Verifying Generic

While Generic is convenient for quickly coming up withrepresentation types it alone isnrsquot enough for our needsas we need to be able to prove that from and to form anisomorphism In pursuit of that goal we define a subclass ofGeneric with two proof methods that state that from andto are mutual inverses

class Generic a rArr VGeneric a where

tf Π (z a) rarr to (from z) sim z

ft Π (r Rep a) rarr from (to r) sim r

Like Generic instances of VGeneric are predictably straight-forward Here is an example of instance for T (with twoconstructors)

instance VGeneric (T a) where

tf (MkT1 _) = Refl

tf (MkT2 _) = Refl

ft (L1 (MkK1 _)) = Refl

ft (R1 (MkK1 _)) = Refl

The implementations of tf and ft are the other sourcesof boilerplate besides from and to For this reason we exposeTemplateHaskell [24] functionality in the verified-classeslibrary to automatically generate VGeneric instances

The class VGeneric plays double duty in the style of proofswe write One of its roles is to serve as a tool for ldquocancellingoutrdquo compositions of from and to as the need often arises tosimplify to (from z) into z or from (to r) into r whenreasoning about generic implementations of class methods Italso serves the role of ensuring that the Generic instance weare using to go between a datatype and its Rep is a legitimateisomorphism Even if the Generic instance we are using isgenerated behind the scenes (say with deriving Generic)we can use VGeneric as an additional sanity check to ensurethat the Generic automation is functioning properly

33 Orderings on Representation TypesWe have now identified the four basic datatypes in Figure 1that can be composed in various ways to form representa-tion types in Generic instances as well as a mechanism toverify that the choice of representation type truly forms anisomorphism The next step is to utilize these tools to writeverified Ord instances and by doing so demonstrate howto obtain a valid total ordering for any algebraic data typeusing this technique

First we define a generic version of (le) by defining Ordinstances for the pattern functor types The instances for U1and K1 are quite straightforward

instance Ord U1 where

MkU1 le MkU1 = True

instance Ord c rArr Ord (K1 c) where

(MkK1 x) le (MkK1 y) = (x le y)

19

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

It is worthwhile to take the time to reflect on what these in-stances mean from a datatype-generic programming perspec-tive The instance for U1 abstracts the idea that a constructorwith no fields is always less than or equal to itself and theinstance for K1 abstracts the idea that for constructors withfields the comparing them amounts to comparing the con-stituent fields The idea of ldquocomparing the constituent fieldsrdquobecomes more precise when we define the Ord instance for()

instance (Ord a Ord b) rArr Ord (a b) where

(x1 y1) le (x2 y2) =

if (x1 == x2) then (y1 le y2) else (x1 le x2)

This instance abstracts the idea that we use a lexicographicordering on products That is we check the first fields ofeach constructor and if they are the same skip them andproceed to the next fields If they are not the same returnthe result of comparing them Importantly this approachscales to any number of fields as this instance iterates overnested uses of () to process the remaining fields

Finally we encode how to compare multiple constructorswith (+)

instance (Ord a Ord b) rArr Ord (a + b) where

(L1 x) le (L1 y) = (x le y)

(R1 x) le (R1 y) = (x le y)

(L1 _) le (R1 _) = True

(R1 _) le (L1 _) = False

This instance abstracts the convention that constructors de-fined earlier in a datatypersquos definition are always less thanconstructors defined later Moreover comparing two of thesame constructor amounts to comparing their respectivefields

Defining these four Ord instances for the pattern functortypes means that any datatype equipped with a Genericinstance can derive an Ord instance cheaply This is becauseit is possible to define an implementation of (le) which wecall genericLeq that works for any instance of Generic

genericLeq (Generic a Ord (Rep a))

rArr a rarr a rarr Bool

genericLeq x y = (from x le from y)

instance Ord a rArr Ord (T a) where

(le) = genericLeq

This works because we can define an order over a datatype interms of the ordering on its representation type to which itis isomorphic Previously we would have had to implementOrd once per datatype with each implementation possiblyrequiring several casesWith datatype-generic programmingwe can reduce the implementation burden to defining (le)for each pattern functor (a one-time cost) defining a Genericinstance per datatype (which are simple and can be auto-mated) and defining an Ord instance per datatype usinggenericLeq (which only requires one line per type)

It is worth clarifying that genericLeq is ldquogenericrdquo in thesense that it provides a canonical implementation of a totalordering on datatypes It is canonical in the sense that itis law-abiding and works for a wide variety of datatypeseven if they have different numbers of constructors or fieldsThat is not to say that this is the only valid total order inexistence For instance we could choose a reverse lexico-graphic ordering that treats rightmost constructors as alwaysbeing less than leftmost constructors We could certainlyaccommodate datatypes with this ordering by inventing aReverseLexicoOrd class and defining appropriate instancesof it for the pattern functor types but in general there mightbe arbitrarily many law-abiding implementations of a classrsquosmethodsIn this work we are primarily concerned with canoni-

cal implementations of class methods as they reflect ldquooff-the-shelfrdquo solutions that programmers reach for when theywant to define instances for their data types in a cheap-and-cheerful manner Therefore we restrict our focus to onegeneric default per class method even though many morelegitimate defaults may exist

34 Proofs over Representation TypesJust as we defined a generic version of (le) over patternfunctors so too can we define generic versions of the totalorder laws by defining VOrd instances Here is a subset ofthe VOrd instance for sums

instance (VOrd a VOrd b) rArr VOrd (a + b) where

leqTransitive s s s Oh Oh =

case (s s s) of

(L1 x L1 y L1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(R1 x R1 y R1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(L1 _ _ R1 _) = Oh

Astute readers will notice that this is the exact same as aVOrd instance that we defined in Section 212 Howevernote that this is the only instance of VOrd that we need toprovide for a sum type Moreover (+) is in some waysthe ldquosimplestrdquo possible sum type so this allows us to managetheir complexity much more than if we were directly writinga VOrd instance for a datatype with many constructors

Another interesting VOrd instance is that for K1 as theseare found at the leaves of a tree of pattern functor types

instance VOrd c rArr VOrd (K1 c) where

leqTransitive k k k Oh Oh =

case (k k k) of

(MkK1 x MkK1 y MkK1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

20

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

In terms of code this instance is quite simple as it simplyuses the underlying VOrd c instance to complete the proofof transitivity What may be less obvious is that the c typeno longer contains any pattern functor types as this is pointwhere we switch to the fields of the datatypersquos constructorsthemselves Put another way the generic computation ldquobot-toms outrdquo at occurrences of K1mdashor if a constructor has nofields it bottoms out at U1

In addition to (+) and K1 we prove the total order lawsfor the remaining two pattern functor types Once this isdone we obtain for free proofs that any Rep in a Genericinstance is a valid total ordering as we can compose thesefour instances as desired to obtain a valid VOrd instanceThis is crucial as it ensures that this technique scales up towhatever size of datatype that we might envision

35 Carrying Over ProofsGiven a VOrd instance for a representation type how canwe relate it back to the original datatype This is wherethe VGeneric class becomes important VGeneric gives usprecisely enough power to take a VOrd proof for one typeand reuse it for the other type in either direction

As a first example we demonstrate how to define a genericimplementation of leqTransitive for any type that imple-ments a VGeneric instance Intuitively we want to showthat a total order on a is transitive by appealing to the factthat the total order on Rep a (to which a is isomorphic) istransitive Computationally this amounts to taking each ofthe three arguments of type a converting them to their Repcounterparts with from and invoking leqTransitive onthem The general structure of this function looks like this

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ =

leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

This would almost typecheck save for one issue the final twoarguments we are attempting to pass to leqTransitive areof types So (x le y) and So (y le z) but since the firstthree arguments involve from this requires that the last twoarguments should actually have the types So (from x lefrom y) and So (from y le from z) respectivelyWhat goes wrong in this example The problem is that

while we attempt to appeal to the transitivity of representa-tion types the type signature does not reflect this A proofthat x le y for instance does not help much when the proofthat we actually care about involves from x and from y Inother words we need to find some way to relate the behaviorof (le) over type a to the behavior of (le) over type Rep aThere is a crude trick available that seemingly gets the

job done we can assume that the implementations of (le)

for a and Rep a coincide exactly That is to say instead ofusing (le) in the type signature of defaultLeqTransitivewhich is not precise enough we instead use genericLeqfrom Section 33 Recall that genericLeq x y = (from xle from y) which is exactly what we need here Thereforewe need only change the type of defaultLeqTransitiveslightly

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (genericLeq x y) rarr So (genericLeq y z)

rarr So (genericLeq x z)

This suffices to make defaultLeqTransitive typecheckbut it comes with a somewhat steep price as it requires that(le) be implemented exactly the same way as genericLeqWe will revisit this limitation in Section 4 and show how toovercome it For the remainder of this section it suffices toassume that (le) and genericLeq are definitionally equalWe use this trick to define default implementations of

the other laws in VOrd as well Using these defaults we canderive the total order laws much more easily than we couldbefore For example showing that T has a lawful total ordernow amount to very little code

instance VOrd a rArr VOrd (T a) where

leqTransitive = defaultLeqTransitive

The generic defaults for VOrd ended up not making useof any specific laws for VGeneric but this is not always thecase for every class A more complicated example is foundin Figure 2 which defines the Semigroup class for datatypessupporting binary associative operations In order to definea generic proof that this operation is associative we mustmake use of the fact that from to = id

defaultAssociative

Π (x y z a)

rarr (VGeneric a VSemigroup (Rep a))

rArr (genericAppend x (genericAppend y z)) sim

(genericAppend (genericAppend x y) z)

defaultAssociative x y z

| Refl larr ft (from x ltgt from y)

Refl larr ft (from y ltgt from z)

= associative (from x) (from y) (from z)

Using ft as a lemma guides the typechecker to realize thatthe conclusion reduces to (to (from x ltgt (from y ltgtfrom z))) sim (to ((from x ltgt from y) ltgt from z))From there appealing to the associativity of (ltgt) causes bothsides of the equation to become equal This example demon-strates that the VGeneric laws not only give us peace of mindthat the conversions to and from a representation type arestructure-preserving but that they are useful computationaltools for deriving generic proofs

21

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 2: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

of boilerplate code Even worse the size of some proofs cangrow super-linearly in the size of the datatypes used as thelength of the proofs can grow extremely quickly due to thesheer number of cases one has to exhaustMany dependently typed languages automate the gener-

ation of boilerplate code by providing tactics as in Coq orLean While heavy use of tactics can make swift work ofmany type-class proofs they can sometimes make it trickyto update existing code as basic changes to the code be-ing verified can cause theorems that rely on the codersquos im-plementation details to suddenly fail Moreover not everydependently typed language is equipped with an advancedtactic system Porting tactic-rich proofs in one language to adifferent language that lacks them can be nontrivial espe-cially if the tactics automate away key steps in the proofsbehind the scenes

Our work aims to introduce a general technique for elimi-nating boilerplate code for type class laws that does not relyon tactics as the primary vehicle for proof automation In-stead we adopt techniques from the field of datatype-genericprogrammingmdashin particular a dialect of generic program-ming that makes use of pattern functors [17 20 36] We lever-age pattern functors to decompose proofs into the smallestalgebraic components possible compose larger proofs out ofthese verified building blocks and then define instances forfull datatypes by reusing the proofs over the pattern functortypesIn fact we can package up this style of code reuse into

generic default implementations of type class laws Thesedefaults are flexible enough to work over any data type thathas an equivalent representation type built out of patternfunctors In general there may be many ways to define an in-stance such that it abides by the classrsquos laws (see Section 33)but generic defaults agrave la pattern functors allow programmersto abstract out canonical ldquooff-the-shelfrdquo implementationsthat work over a wide variety of data types These canonicaldefaults are highly usefulmdashfor instance the n-body simu-lation in Vazou et al [31] uses a canonical instance of theMonoid classmdashso they receive the most attention in our workIn particular the primary contributions of this paper are

bull We show how to derive implementations of type-classmethods and laws with the pattern functor approachto datatype-generic programming (Section 3)bull We provide the first datatype-generic approach to ver-ifying class laws that accommodates existing instances(not written in any special style) while still providingsubstantial proof automation (Section 4)bull We present a prototype implementation of these ideasand evaluate the lines of code required and impact oncompile time (Section 5)

Our prototype implementation is a Haskell library whichwe call verified-classes The verified-classes libraryprovides generic implementations of laws for several type

classes in Haskellrsquos base library (previously asserted as un-verified comments) We additionally consider what an imple-mentation of these ideas would look like in other languagesin Section 6

2 BackgroundDependent types offer a vehicle for verifying type class lawsby simply defining additional class methods with proof obli-gations In this section we flesh out a complete example ofa verified type class and demonstrate how one can define in-stances for it using progressively larger datatypes As the sizeof the datatypes increases it will become apparent that thereis a problem of scale since the number of cases required tocomplete the corresponding proofs increases quadratically

21 A Tour of Verified ClassesThe goal of our work is to present a technique that can beported to any dependently typed programming languagethat supports type classes (a topic that we will revisit inSection 6) In pursuit of this goal in this paper we adoptthe convention of using a minimalistic language that resem-bles Haskell We say ldquoresembles Haskellrdquo rather than ldquoisHaskellrdquo since in practice our evaluation uses the single-tons technique [13] to encode dependent types in HaskellThis process is quite straightforward and we invite the inter-ested reader to Appendix A1 [21] for the full details of howthis works However the singletons encoding introduces acertain degree of syntactic noise so for the sake of presen-tation clarity we use a syntax that is closer to most otherdependently typed languages (One can imagine that we arewriting code in a hypothetical Dependent Haskell [12])

211 ClassesAs a running example we use a minimal version of the Ordtype class which describes datatypes that support boolean-returning comparison operations

class Eq a rArr Ord a where

(le) a rarr a rarr Bool

Where Eq and Bool are defined to be

class Eq a where data Bool = False | True

(==) a rarr a rarr Bool

In order for a datatype with an Ord instance to be consideredverified we require that the implementation of (le) mustbehave like a total order That is to say it must satisfy thefollowing four laws

Reflexivity forallx x le xTransitivity forallx y z x le y le z rArr x le z

Antisymmetry forallx y x le y and y le x rArr x = yTotality forallx y x le y or y le x

Using dependent types we encode these laws as proof meth-ods of a type class We define a new class VOrd (short for

16

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

ldquoverified Ordrdquo) to represent the group of Ord instances thatare known to be lawful total orders1

class Ord a rArr VOrd a where

leqReflexive Π (x a) rarr (x le x) sim True

leqTransitive

Π (x y z a)

rarr (x le y) sim True rarr (y le z) sim True

rarr (x le z) sim True

leqAntisymmetric

Π (x y a)

rarr (x le y) sim True rarr (y le x) sim True

rarr (x == y) sim True

leqTotal

Π (x y a)

rarr Either ((x le y) sim True)

((y le x) sim True (x == y) sim False)

The methods of VOrd translate the laws above into typesignatures making use of the propositional equality type(sim) which reflects the judgment that two types are equalinto a type of its own

data (sim) forall k k rarr k rarr Type where

Refl a sim a

For the sake of making these type signatures a little morebrief we introduce an auxiliary definition that reflects True-valued boolean judgments into types

data So Bool rarr Type where

Oh So True

Using So we can tighten up the presentation of VOrd some-what

class Ord a rArr VOrd a where

leqReflexive Π (x a) rarr So (x le x)

leqTransitive

Π (x y z a)

rarr So (x le y) rarr So (y le z) rarr So (x le z)

leqAntisymmetric

Π (x y a)

rarr So (x le y) rarr So (y le x) rarr So (x == y)

leqTotal

Π (x y a)

rarr Either (So (x le y))

(So (y le x) So (not (x == y)))

212 Examples of InstancesTo demonstrate VOrd in action we first define an extremelysimple datatype along with accompanying instances of Eqand Ord

1Alternatively we could inline the methods of VOrd directly into Ord Wechoose not to do so here since many languages such as Haskell make useof certain unlawful instances such as the Ord instances for Floats andDoubles

data T a = MkT1 a

instance Eq a rArr Eq (T a) where

(MkT1 x) == (MkT1 y) = (x == y)

instance Ord a rArr Ord (T a) where

(MkT1 x) le (MkT1 y) = x le y

Unsurprisingly this implementation of (le) comprises a totalorder We verify this claim by implementing a VOrd instancefor T like so

instance VOrd a rArr VOrd (T a) where

leqReflexive (MkT1 x)

| Oh larr leqReflexive x = Oh

leqTransitive (MkT1 x) (MkT1 y) (MkT1 z) Oh Oh

| Oh larr leqTransitive x y z Oh Oh = Oh

leqAntisymmetric (MkT1 x) (MkT1 y) Oh Oh

| Oh larr leqAntisymmetric x y Oh Oh = Oh

leqTotal (MkT1 x) (MkT1 y) =

case leqTotal x y of

Left Oh rarr Left Oh

Right (Oh Oh) rarr Right (Oh Oh)

The implementations of these proofs outline the basic ap-proach to verifying algebraic properties For each propertywe wish to prove we proceed by cases on T (encoded us-ing pattern matching) appeal to our induction hypothesis(encoded using recursion) and then complete the proof

This instance was relatively painless to write as therewas only one case to consider the MkT1 constructor of THowever the size of the proofs grows noticeably when weincrease the size of T For instance consider what happenswhen we add another constructor

data T a = MkT1 a | MkT2 a

First we must adjust our Eq and Ord instances accordinglyWe pick the convention that MkT1 is always less than MkT2

instance Eq a rArr Eq (T a) where

(MkT1 x) == (MkT1 y) = (x == y)

(MkT2 x) == (MkT2 y) = (x == y)

(MkT1 _) == (MkT2 _) = False

(MkT2 _) == (MkT1 _) = False

instance Ord a rArr Ord (T a) where

(MkT1 x) le (MkT1 y) = (x le y)

(MkT2 x) le (MkT2 y) = (x le y)

(MkT1 _) le (MkT2 _) = True

(MkT2 _) le (MkT1 _) = False

Next now that the code that wersquore verifying has changedwe must synchronize the accompanying proofs in the VOrdinstance We start with the proof of reflexivity

instance VOrd a rArr VOrd (T a) where

leqReflexive (MkT1 x)

| Oh larr leqReflexive x = Oh

leqReflexive (MkT2 x)

| Oh larr leqReflexive x = Oh

17

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Updating leqReflexive ended up not requiring that mucheffort as we only needed to have one extra case for the addi-tional constructor MkT2 Updating leqTransitive howeverrequires more effort

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

In addition to requiring an extra case for the combination ofarguments where all of them are MkT2 we now require anadditional case to cover the two combinations of argumentswhere the first argument is MkT1 and the third argument isMkT2 The four other possible combinations of argumentswere ruled out by dependent pattern matching as the typesystem concluded that they were impossible-to-reach cases(for example if t = MkT2 _ and t = MkT1 _ then it can-not be the case that t le t is True) In Haskell we do notneed to write these cases out at all although some otherlanguages may require explicit ldquoabsurdrdquo cases (eg using the() pattern in Agda)

Having to add one more case might not seem that bur-densome but the number of cases one has to supply forleqTransitive grows quickly as we add more and moreconstructors For example if we added a third constructorMkT3 to T then after updating the Eq and Ord instances (us-ing the convention that MkT1 is always less than MkT2 andMkT2 is always less than MkT3) this is what the proof oftransitivity would become

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT3 x MkT3 y MkT3 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

(MkT1 _ _ MkT3 _) = Oh

(MkT2 _ _ MkT3 _) = Oh

This time we have six cases If we add a fourth constructorwe would have 10 cases and if we add a fifth constructor wewould have fifteen cases In general if we had n constructorsthen leqTransitivewould require n+

(n2)= 1

2 (n2+n) cases

class Generic a where

type Rep a Type

from a rarr Rep a

to Rep a rarr a

data U1 = MkU1

newtype K1 c = MkK1 c

data a b = a b

data a + b = L1 a | R1 b

Figure 1A slightly simplified presentation of the definitionsfrom Haskellrsquos GHCGenerics library on which we base ourapproach to datatype-generic programming

which grows quadratically This has the potential to makescaling up proofs extremely tiresomePerhaps even more troublesome than the size of these

proofs themselves is the fact that most of these cases aresheer boilerplate For instance leqTransitive follows avery predictable pattern For combinations of argumentswhere all the constructors are the same recurse and forcombinations where there are different constructors it mustbe the case that the first argument is less than or equal to thethird argument so immediately return Oh This is routinecode that is begging to be automated with a proof-reusetechnique

3 Verified Instances GenericallyHaving seen the tedium of manually constructing certainproofs we present a solution Notably our solution does notrequire a tremendous amount of support from the languageitself (in terms of tactics or metaprogramming) Instead werely on techniques that could be ported to any dependentlytyped programming language that at a minimum supportstype classes2

We adapt an approach from the field of datatype-genericprogramming where we take an algebraic datatype and con-struct a representation type which is isomorphic to it Therepresentation type itself is a composition of smaller patternfunctor datatypes that encode primitive ldquobuilding blocksrdquo ofother datatypes such as products sums and individual fieldsWe also establish a type class for witnessing the isomorphismbetween a datatype and its representation type [17 20 36]By leveraging these tools we shift the burden of proof

from the original datatype (which may be arbitrarily com-plex) to the handful of simple pattern functor types thatconstitute its representation type This way we are able toprove properties for a wide range of possible datatypes bysimply proving the same properties for a finite number ofldquobuilding blockrdquo types

31 A Primer on Datatype-Generic ProgrammingTo build up representation types we build upon the tech-niques from Magalhatildees et al [17] which influenced the de-sign of Haskellrsquos popular GHCGenerics library Figure 12It is possible to further reduce the amount of code needed by generat-ing boilerplate code with metaprogramming techniques (eg TemplateHaskell [24]) but support for metaprogramming is not a strict requirement

18

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

presents the central definitions used in this style of datatype-generic programmingThe Generic type class is the focal point of the library

A datatype with a Generic instance comes equipped witha Rep the representation type and functions from and towhich witness the isomorphism between the representationtype and the original datatype The Rep type is always somecombination of the following pattern functor types3

1 The U1 type represents constructors with no fields2 The K1 type represents a single field in a constructor3 The () type (pronounced ldquoproductrdquo) represents

two consecutive fields in a constructor4 The (+) type (pronounced ldquosumrdquo) represents the

choice between two consecutive constructorsThis is best explained with an example so recall this ver-

sion of the T data type from Section 212

data T a = MkT1 a | MkT2 a

We define its canonical Generic instance like soinstance Generic (T a) where

type Rep (T a) = K1 a + K1 a

from (MkT1 x) = L1 (MkK1 x)

from (MkT2 x) = R1 (MkK1 x)

to (L1 (MkK1 x)) = MkT1 x

to (R1 (MkK1 x)) = MkT2 x

Here we see that because T has two constructors MkT1 andMkT2 the (+) type is used once to represent the choicebetween them The field of type a in each constructor is inturn represented with a K1 type The same sort of patternwould follow if we added more constructors to T For exam-ple here is how the Rep instance would appear if there werethree constructors 4

data T a = MkT1 a | MkT2 a | MkT3 Bool

instance Generic (T a) where

type Rep (T a) = K1 a + (K1 a + K1 Bool)

Each constructor corresponds to another use of (+) todenote another choice of constructor Despite the size of theT type increasing the number of distinct datatypes in its Rephas not changed which is an important property

The implementations of from and to are are entirely me-chanical to implement and constitute one of the few sourcesof boilerplate in this style of datatype-generic programmingLanguages like Haskell offer a metaprogramming facility(deriving Generic) for generating this boilerplate althoughit can just as well be written by hand3There is another pattern functor type M1 which is used to attach metadatasuch as constructor names fixities etc For the sake of simplicity we leaveit out of the discussion in this section but we revisit it in 514Although (+) is right-associative we have added explicit parenthesesin this Rep instance for clarity In general a canonical Generic instancebalances nested uses of (+) and () so that it is always possible togo from the root of the representation type to a leaf in logarithmic (ratherthan linear) time

32 Verifying Generic

While Generic is convenient for quickly coming up withrepresentation types it alone isnrsquot enough for our needsas we need to be able to prove that from and to form anisomorphism In pursuit of that goal we define a subclass ofGeneric with two proof methods that state that from andto are mutual inverses

class Generic a rArr VGeneric a where

tf Π (z a) rarr to (from z) sim z

ft Π (r Rep a) rarr from (to r) sim r

Like Generic instances of VGeneric are predictably straight-forward Here is an example of instance for T (with twoconstructors)

instance VGeneric (T a) where

tf (MkT1 _) = Refl

tf (MkT2 _) = Refl

ft (L1 (MkK1 _)) = Refl

ft (R1 (MkK1 _)) = Refl

The implementations of tf and ft are the other sourcesof boilerplate besides from and to For this reason we exposeTemplateHaskell [24] functionality in the verified-classeslibrary to automatically generate VGeneric instances

The class VGeneric plays double duty in the style of proofswe write One of its roles is to serve as a tool for ldquocancellingoutrdquo compositions of from and to as the need often arises tosimplify to (from z) into z or from (to r) into r whenreasoning about generic implementations of class methods Italso serves the role of ensuring that the Generic instance weare using to go between a datatype and its Rep is a legitimateisomorphism Even if the Generic instance we are using isgenerated behind the scenes (say with deriving Generic)we can use VGeneric as an additional sanity check to ensurethat the Generic automation is functioning properly

33 Orderings on Representation TypesWe have now identified the four basic datatypes in Figure 1that can be composed in various ways to form representa-tion types in Generic instances as well as a mechanism toverify that the choice of representation type truly forms anisomorphism The next step is to utilize these tools to writeverified Ord instances and by doing so demonstrate howto obtain a valid total ordering for any algebraic data typeusing this technique

First we define a generic version of (le) by defining Ordinstances for the pattern functor types The instances for U1and K1 are quite straightforward

instance Ord U1 where

MkU1 le MkU1 = True

instance Ord c rArr Ord (K1 c) where

(MkK1 x) le (MkK1 y) = (x le y)

19

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

It is worthwhile to take the time to reflect on what these in-stances mean from a datatype-generic programming perspec-tive The instance for U1 abstracts the idea that a constructorwith no fields is always less than or equal to itself and theinstance for K1 abstracts the idea that for constructors withfields the comparing them amounts to comparing the con-stituent fields The idea of ldquocomparing the constituent fieldsrdquobecomes more precise when we define the Ord instance for()

instance (Ord a Ord b) rArr Ord (a b) where

(x1 y1) le (x2 y2) =

if (x1 == x2) then (y1 le y2) else (x1 le x2)

This instance abstracts the idea that we use a lexicographicordering on products That is we check the first fields ofeach constructor and if they are the same skip them andproceed to the next fields If they are not the same returnthe result of comparing them Importantly this approachscales to any number of fields as this instance iterates overnested uses of () to process the remaining fields

Finally we encode how to compare multiple constructorswith (+)

instance (Ord a Ord b) rArr Ord (a + b) where

(L1 x) le (L1 y) = (x le y)

(R1 x) le (R1 y) = (x le y)

(L1 _) le (R1 _) = True

(R1 _) le (L1 _) = False

This instance abstracts the convention that constructors de-fined earlier in a datatypersquos definition are always less thanconstructors defined later Moreover comparing two of thesame constructor amounts to comparing their respectivefields

Defining these four Ord instances for the pattern functortypes means that any datatype equipped with a Genericinstance can derive an Ord instance cheaply This is becauseit is possible to define an implementation of (le) which wecall genericLeq that works for any instance of Generic

genericLeq (Generic a Ord (Rep a))

rArr a rarr a rarr Bool

genericLeq x y = (from x le from y)

instance Ord a rArr Ord (T a) where

(le) = genericLeq

This works because we can define an order over a datatype interms of the ordering on its representation type to which itis isomorphic Previously we would have had to implementOrd once per datatype with each implementation possiblyrequiring several casesWith datatype-generic programmingwe can reduce the implementation burden to defining (le)for each pattern functor (a one-time cost) defining a Genericinstance per datatype (which are simple and can be auto-mated) and defining an Ord instance per datatype usinggenericLeq (which only requires one line per type)

It is worth clarifying that genericLeq is ldquogenericrdquo in thesense that it provides a canonical implementation of a totalordering on datatypes It is canonical in the sense that itis law-abiding and works for a wide variety of datatypeseven if they have different numbers of constructors or fieldsThat is not to say that this is the only valid total order inexistence For instance we could choose a reverse lexico-graphic ordering that treats rightmost constructors as alwaysbeing less than leftmost constructors We could certainlyaccommodate datatypes with this ordering by inventing aReverseLexicoOrd class and defining appropriate instancesof it for the pattern functor types but in general there mightbe arbitrarily many law-abiding implementations of a classrsquosmethodsIn this work we are primarily concerned with canoni-

cal implementations of class methods as they reflect ldquooff-the-shelfrdquo solutions that programmers reach for when theywant to define instances for their data types in a cheap-and-cheerful manner Therefore we restrict our focus to onegeneric default per class method even though many morelegitimate defaults may exist

34 Proofs over Representation TypesJust as we defined a generic version of (le) over patternfunctors so too can we define generic versions of the totalorder laws by defining VOrd instances Here is a subset ofthe VOrd instance for sums

instance (VOrd a VOrd b) rArr VOrd (a + b) where

leqTransitive s s s Oh Oh =

case (s s s) of

(L1 x L1 y L1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(R1 x R1 y R1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(L1 _ _ R1 _) = Oh

Astute readers will notice that this is the exact same as aVOrd instance that we defined in Section 212 Howevernote that this is the only instance of VOrd that we need toprovide for a sum type Moreover (+) is in some waysthe ldquosimplestrdquo possible sum type so this allows us to managetheir complexity much more than if we were directly writinga VOrd instance for a datatype with many constructors

Another interesting VOrd instance is that for K1 as theseare found at the leaves of a tree of pattern functor types

instance VOrd c rArr VOrd (K1 c) where

leqTransitive k k k Oh Oh =

case (k k k) of

(MkK1 x MkK1 y MkK1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

20

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

In terms of code this instance is quite simple as it simplyuses the underlying VOrd c instance to complete the proofof transitivity What may be less obvious is that the c typeno longer contains any pattern functor types as this is pointwhere we switch to the fields of the datatypersquos constructorsthemselves Put another way the generic computation ldquobot-toms outrdquo at occurrences of K1mdashor if a constructor has nofields it bottoms out at U1

In addition to (+) and K1 we prove the total order lawsfor the remaining two pattern functor types Once this isdone we obtain for free proofs that any Rep in a Genericinstance is a valid total ordering as we can compose thesefour instances as desired to obtain a valid VOrd instanceThis is crucial as it ensures that this technique scales up towhatever size of datatype that we might envision

35 Carrying Over ProofsGiven a VOrd instance for a representation type how canwe relate it back to the original datatype This is wherethe VGeneric class becomes important VGeneric gives usprecisely enough power to take a VOrd proof for one typeand reuse it for the other type in either direction

As a first example we demonstrate how to define a genericimplementation of leqTransitive for any type that imple-ments a VGeneric instance Intuitively we want to showthat a total order on a is transitive by appealing to the factthat the total order on Rep a (to which a is isomorphic) istransitive Computationally this amounts to taking each ofthe three arguments of type a converting them to their Repcounterparts with from and invoking leqTransitive onthem The general structure of this function looks like this

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ =

leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

This would almost typecheck save for one issue the final twoarguments we are attempting to pass to leqTransitive areof types So (x le y) and So (y le z) but since the firstthree arguments involve from this requires that the last twoarguments should actually have the types So (from x lefrom y) and So (from y le from z) respectivelyWhat goes wrong in this example The problem is that

while we attempt to appeal to the transitivity of representa-tion types the type signature does not reflect this A proofthat x le y for instance does not help much when the proofthat we actually care about involves from x and from y Inother words we need to find some way to relate the behaviorof (le) over type a to the behavior of (le) over type Rep aThere is a crude trick available that seemingly gets the

job done we can assume that the implementations of (le)

for a and Rep a coincide exactly That is to say instead ofusing (le) in the type signature of defaultLeqTransitivewhich is not precise enough we instead use genericLeqfrom Section 33 Recall that genericLeq x y = (from xle from y) which is exactly what we need here Thereforewe need only change the type of defaultLeqTransitiveslightly

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (genericLeq x y) rarr So (genericLeq y z)

rarr So (genericLeq x z)

This suffices to make defaultLeqTransitive typecheckbut it comes with a somewhat steep price as it requires that(le) be implemented exactly the same way as genericLeqWe will revisit this limitation in Section 4 and show how toovercome it For the remainder of this section it suffices toassume that (le) and genericLeq are definitionally equalWe use this trick to define default implementations of

the other laws in VOrd as well Using these defaults we canderive the total order laws much more easily than we couldbefore For example showing that T has a lawful total ordernow amount to very little code

instance VOrd a rArr VOrd (T a) where

leqTransitive = defaultLeqTransitive

The generic defaults for VOrd ended up not making useof any specific laws for VGeneric but this is not always thecase for every class A more complicated example is foundin Figure 2 which defines the Semigroup class for datatypessupporting binary associative operations In order to definea generic proof that this operation is associative we mustmake use of the fact that from to = id

defaultAssociative

Π (x y z a)

rarr (VGeneric a VSemigroup (Rep a))

rArr (genericAppend x (genericAppend y z)) sim

(genericAppend (genericAppend x y) z)

defaultAssociative x y z

| Refl larr ft (from x ltgt from y)

Refl larr ft (from y ltgt from z)

= associative (from x) (from y) (from z)

Using ft as a lemma guides the typechecker to realize thatthe conclusion reduces to (to (from x ltgt (from y ltgtfrom z))) sim (to ((from x ltgt from y) ltgt from z))From there appealing to the associativity of (ltgt) causes bothsides of the equation to become equal This example demon-strates that the VGeneric laws not only give us peace of mindthat the conversions to and from a representation type arestructure-preserving but that they are useful computationaltools for deriving generic proofs

21

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 3: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

ldquoverified Ordrdquo) to represent the group of Ord instances thatare known to be lawful total orders1

class Ord a rArr VOrd a where

leqReflexive Π (x a) rarr (x le x) sim True

leqTransitive

Π (x y z a)

rarr (x le y) sim True rarr (y le z) sim True

rarr (x le z) sim True

leqAntisymmetric

Π (x y a)

rarr (x le y) sim True rarr (y le x) sim True

rarr (x == y) sim True

leqTotal

Π (x y a)

rarr Either ((x le y) sim True)

((y le x) sim True (x == y) sim False)

The methods of VOrd translate the laws above into typesignatures making use of the propositional equality type(sim) which reflects the judgment that two types are equalinto a type of its own

data (sim) forall k k rarr k rarr Type where

Refl a sim a

For the sake of making these type signatures a little morebrief we introduce an auxiliary definition that reflects True-valued boolean judgments into types

data So Bool rarr Type where

Oh So True

Using So we can tighten up the presentation of VOrd some-what

class Ord a rArr VOrd a where

leqReflexive Π (x a) rarr So (x le x)

leqTransitive

Π (x y z a)

rarr So (x le y) rarr So (y le z) rarr So (x le z)

leqAntisymmetric

Π (x y a)

rarr So (x le y) rarr So (y le x) rarr So (x == y)

leqTotal

Π (x y a)

rarr Either (So (x le y))

(So (y le x) So (not (x == y)))

212 Examples of InstancesTo demonstrate VOrd in action we first define an extremelysimple datatype along with accompanying instances of Eqand Ord

1Alternatively we could inline the methods of VOrd directly into Ord Wechoose not to do so here since many languages such as Haskell make useof certain unlawful instances such as the Ord instances for Floats andDoubles

data T a = MkT1 a

instance Eq a rArr Eq (T a) where

(MkT1 x) == (MkT1 y) = (x == y)

instance Ord a rArr Ord (T a) where

(MkT1 x) le (MkT1 y) = x le y

Unsurprisingly this implementation of (le) comprises a totalorder We verify this claim by implementing a VOrd instancefor T like so

instance VOrd a rArr VOrd (T a) where

leqReflexive (MkT1 x)

| Oh larr leqReflexive x = Oh

leqTransitive (MkT1 x) (MkT1 y) (MkT1 z) Oh Oh

| Oh larr leqTransitive x y z Oh Oh = Oh

leqAntisymmetric (MkT1 x) (MkT1 y) Oh Oh

| Oh larr leqAntisymmetric x y Oh Oh = Oh

leqTotal (MkT1 x) (MkT1 y) =

case leqTotal x y of

Left Oh rarr Left Oh

Right (Oh Oh) rarr Right (Oh Oh)

The implementations of these proofs outline the basic ap-proach to verifying algebraic properties For each propertywe wish to prove we proceed by cases on T (encoded us-ing pattern matching) appeal to our induction hypothesis(encoded using recursion) and then complete the proof

This instance was relatively painless to write as therewas only one case to consider the MkT1 constructor of THowever the size of the proofs grows noticeably when weincrease the size of T For instance consider what happenswhen we add another constructor

data T a = MkT1 a | MkT2 a

First we must adjust our Eq and Ord instances accordinglyWe pick the convention that MkT1 is always less than MkT2

instance Eq a rArr Eq (T a) where

(MkT1 x) == (MkT1 y) = (x == y)

(MkT2 x) == (MkT2 y) = (x == y)

(MkT1 _) == (MkT2 _) = False

(MkT2 _) == (MkT1 _) = False

instance Ord a rArr Ord (T a) where

(MkT1 x) le (MkT1 y) = (x le y)

(MkT2 x) le (MkT2 y) = (x le y)

(MkT1 _) le (MkT2 _) = True

(MkT2 _) le (MkT1 _) = False

Next now that the code that wersquore verifying has changedwe must synchronize the accompanying proofs in the VOrdinstance We start with the proof of reflexivity

instance VOrd a rArr VOrd (T a) where

leqReflexive (MkT1 x)

| Oh larr leqReflexive x = Oh

leqReflexive (MkT2 x)

| Oh larr leqReflexive x = Oh

17

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Updating leqReflexive ended up not requiring that mucheffort as we only needed to have one extra case for the addi-tional constructor MkT2 Updating leqTransitive howeverrequires more effort

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

In addition to requiring an extra case for the combination ofarguments where all of them are MkT2 we now require anadditional case to cover the two combinations of argumentswhere the first argument is MkT1 and the third argument isMkT2 The four other possible combinations of argumentswere ruled out by dependent pattern matching as the typesystem concluded that they were impossible-to-reach cases(for example if t = MkT2 _ and t = MkT1 _ then it can-not be the case that t le t is True) In Haskell we do notneed to write these cases out at all although some otherlanguages may require explicit ldquoabsurdrdquo cases (eg using the() pattern in Agda)

Having to add one more case might not seem that bur-densome but the number of cases one has to supply forleqTransitive grows quickly as we add more and moreconstructors For example if we added a third constructorMkT3 to T then after updating the Eq and Ord instances (us-ing the convention that MkT1 is always less than MkT2 andMkT2 is always less than MkT3) this is what the proof oftransitivity would become

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT3 x MkT3 y MkT3 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

(MkT1 _ _ MkT3 _) = Oh

(MkT2 _ _ MkT3 _) = Oh

This time we have six cases If we add a fourth constructorwe would have 10 cases and if we add a fifth constructor wewould have fifteen cases In general if we had n constructorsthen leqTransitivewould require n+

(n2)= 1

2 (n2+n) cases

class Generic a where

type Rep a Type

from a rarr Rep a

to Rep a rarr a

data U1 = MkU1

newtype K1 c = MkK1 c

data a b = a b

data a + b = L1 a | R1 b

Figure 1A slightly simplified presentation of the definitionsfrom Haskellrsquos GHCGenerics library on which we base ourapproach to datatype-generic programming

which grows quadratically This has the potential to makescaling up proofs extremely tiresomePerhaps even more troublesome than the size of these

proofs themselves is the fact that most of these cases aresheer boilerplate For instance leqTransitive follows avery predictable pattern For combinations of argumentswhere all the constructors are the same recurse and forcombinations where there are different constructors it mustbe the case that the first argument is less than or equal to thethird argument so immediately return Oh This is routinecode that is begging to be automated with a proof-reusetechnique

3 Verified Instances GenericallyHaving seen the tedium of manually constructing certainproofs we present a solution Notably our solution does notrequire a tremendous amount of support from the languageitself (in terms of tactics or metaprogramming) Instead werely on techniques that could be ported to any dependentlytyped programming language that at a minimum supportstype classes2

We adapt an approach from the field of datatype-genericprogramming where we take an algebraic datatype and con-struct a representation type which is isomorphic to it Therepresentation type itself is a composition of smaller patternfunctor datatypes that encode primitive ldquobuilding blocksrdquo ofother datatypes such as products sums and individual fieldsWe also establish a type class for witnessing the isomorphismbetween a datatype and its representation type [17 20 36]By leveraging these tools we shift the burden of proof

from the original datatype (which may be arbitrarily com-plex) to the handful of simple pattern functor types thatconstitute its representation type This way we are able toprove properties for a wide range of possible datatypes bysimply proving the same properties for a finite number ofldquobuilding blockrdquo types

31 A Primer on Datatype-Generic ProgrammingTo build up representation types we build upon the tech-niques from Magalhatildees et al [17] which influenced the de-sign of Haskellrsquos popular GHCGenerics library Figure 12It is possible to further reduce the amount of code needed by generat-ing boilerplate code with metaprogramming techniques (eg TemplateHaskell [24]) but support for metaprogramming is not a strict requirement

18

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

presents the central definitions used in this style of datatype-generic programmingThe Generic type class is the focal point of the library

A datatype with a Generic instance comes equipped witha Rep the representation type and functions from and towhich witness the isomorphism between the representationtype and the original datatype The Rep type is always somecombination of the following pattern functor types3

1 The U1 type represents constructors with no fields2 The K1 type represents a single field in a constructor3 The () type (pronounced ldquoproductrdquo) represents

two consecutive fields in a constructor4 The (+) type (pronounced ldquosumrdquo) represents the

choice between two consecutive constructorsThis is best explained with an example so recall this ver-

sion of the T data type from Section 212

data T a = MkT1 a | MkT2 a

We define its canonical Generic instance like soinstance Generic (T a) where

type Rep (T a) = K1 a + K1 a

from (MkT1 x) = L1 (MkK1 x)

from (MkT2 x) = R1 (MkK1 x)

to (L1 (MkK1 x)) = MkT1 x

to (R1 (MkK1 x)) = MkT2 x

Here we see that because T has two constructors MkT1 andMkT2 the (+) type is used once to represent the choicebetween them The field of type a in each constructor is inturn represented with a K1 type The same sort of patternwould follow if we added more constructors to T For exam-ple here is how the Rep instance would appear if there werethree constructors 4

data T a = MkT1 a | MkT2 a | MkT3 Bool

instance Generic (T a) where

type Rep (T a) = K1 a + (K1 a + K1 Bool)

Each constructor corresponds to another use of (+) todenote another choice of constructor Despite the size of theT type increasing the number of distinct datatypes in its Rephas not changed which is an important property

The implementations of from and to are are entirely me-chanical to implement and constitute one of the few sourcesof boilerplate in this style of datatype-generic programmingLanguages like Haskell offer a metaprogramming facility(deriving Generic) for generating this boilerplate althoughit can just as well be written by hand3There is another pattern functor type M1 which is used to attach metadatasuch as constructor names fixities etc For the sake of simplicity we leaveit out of the discussion in this section but we revisit it in 514Although (+) is right-associative we have added explicit parenthesesin this Rep instance for clarity In general a canonical Generic instancebalances nested uses of (+) and () so that it is always possible togo from the root of the representation type to a leaf in logarithmic (ratherthan linear) time

32 Verifying Generic

While Generic is convenient for quickly coming up withrepresentation types it alone isnrsquot enough for our needsas we need to be able to prove that from and to form anisomorphism In pursuit of that goal we define a subclass ofGeneric with two proof methods that state that from andto are mutual inverses

class Generic a rArr VGeneric a where

tf Π (z a) rarr to (from z) sim z

ft Π (r Rep a) rarr from (to r) sim r

Like Generic instances of VGeneric are predictably straight-forward Here is an example of instance for T (with twoconstructors)

instance VGeneric (T a) where

tf (MkT1 _) = Refl

tf (MkT2 _) = Refl

ft (L1 (MkK1 _)) = Refl

ft (R1 (MkK1 _)) = Refl

The implementations of tf and ft are the other sourcesof boilerplate besides from and to For this reason we exposeTemplateHaskell [24] functionality in the verified-classeslibrary to automatically generate VGeneric instances

The class VGeneric plays double duty in the style of proofswe write One of its roles is to serve as a tool for ldquocancellingoutrdquo compositions of from and to as the need often arises tosimplify to (from z) into z or from (to r) into r whenreasoning about generic implementations of class methods Italso serves the role of ensuring that the Generic instance weare using to go between a datatype and its Rep is a legitimateisomorphism Even if the Generic instance we are using isgenerated behind the scenes (say with deriving Generic)we can use VGeneric as an additional sanity check to ensurethat the Generic automation is functioning properly

33 Orderings on Representation TypesWe have now identified the four basic datatypes in Figure 1that can be composed in various ways to form representa-tion types in Generic instances as well as a mechanism toverify that the choice of representation type truly forms anisomorphism The next step is to utilize these tools to writeverified Ord instances and by doing so demonstrate howto obtain a valid total ordering for any algebraic data typeusing this technique

First we define a generic version of (le) by defining Ordinstances for the pattern functor types The instances for U1and K1 are quite straightforward

instance Ord U1 where

MkU1 le MkU1 = True

instance Ord c rArr Ord (K1 c) where

(MkK1 x) le (MkK1 y) = (x le y)

19

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

It is worthwhile to take the time to reflect on what these in-stances mean from a datatype-generic programming perspec-tive The instance for U1 abstracts the idea that a constructorwith no fields is always less than or equal to itself and theinstance for K1 abstracts the idea that for constructors withfields the comparing them amounts to comparing the con-stituent fields The idea of ldquocomparing the constituent fieldsrdquobecomes more precise when we define the Ord instance for()

instance (Ord a Ord b) rArr Ord (a b) where

(x1 y1) le (x2 y2) =

if (x1 == x2) then (y1 le y2) else (x1 le x2)

This instance abstracts the idea that we use a lexicographicordering on products That is we check the first fields ofeach constructor and if they are the same skip them andproceed to the next fields If they are not the same returnthe result of comparing them Importantly this approachscales to any number of fields as this instance iterates overnested uses of () to process the remaining fields

Finally we encode how to compare multiple constructorswith (+)

instance (Ord a Ord b) rArr Ord (a + b) where

(L1 x) le (L1 y) = (x le y)

(R1 x) le (R1 y) = (x le y)

(L1 _) le (R1 _) = True

(R1 _) le (L1 _) = False

This instance abstracts the convention that constructors de-fined earlier in a datatypersquos definition are always less thanconstructors defined later Moreover comparing two of thesame constructor amounts to comparing their respectivefields

Defining these four Ord instances for the pattern functortypes means that any datatype equipped with a Genericinstance can derive an Ord instance cheaply This is becauseit is possible to define an implementation of (le) which wecall genericLeq that works for any instance of Generic

genericLeq (Generic a Ord (Rep a))

rArr a rarr a rarr Bool

genericLeq x y = (from x le from y)

instance Ord a rArr Ord (T a) where

(le) = genericLeq

This works because we can define an order over a datatype interms of the ordering on its representation type to which itis isomorphic Previously we would have had to implementOrd once per datatype with each implementation possiblyrequiring several casesWith datatype-generic programmingwe can reduce the implementation burden to defining (le)for each pattern functor (a one-time cost) defining a Genericinstance per datatype (which are simple and can be auto-mated) and defining an Ord instance per datatype usinggenericLeq (which only requires one line per type)

It is worth clarifying that genericLeq is ldquogenericrdquo in thesense that it provides a canonical implementation of a totalordering on datatypes It is canonical in the sense that itis law-abiding and works for a wide variety of datatypeseven if they have different numbers of constructors or fieldsThat is not to say that this is the only valid total order inexistence For instance we could choose a reverse lexico-graphic ordering that treats rightmost constructors as alwaysbeing less than leftmost constructors We could certainlyaccommodate datatypes with this ordering by inventing aReverseLexicoOrd class and defining appropriate instancesof it for the pattern functor types but in general there mightbe arbitrarily many law-abiding implementations of a classrsquosmethodsIn this work we are primarily concerned with canoni-

cal implementations of class methods as they reflect ldquooff-the-shelfrdquo solutions that programmers reach for when theywant to define instances for their data types in a cheap-and-cheerful manner Therefore we restrict our focus to onegeneric default per class method even though many morelegitimate defaults may exist

34 Proofs over Representation TypesJust as we defined a generic version of (le) over patternfunctors so too can we define generic versions of the totalorder laws by defining VOrd instances Here is a subset ofthe VOrd instance for sums

instance (VOrd a VOrd b) rArr VOrd (a + b) where

leqTransitive s s s Oh Oh =

case (s s s) of

(L1 x L1 y L1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(R1 x R1 y R1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(L1 _ _ R1 _) = Oh

Astute readers will notice that this is the exact same as aVOrd instance that we defined in Section 212 Howevernote that this is the only instance of VOrd that we need toprovide for a sum type Moreover (+) is in some waysthe ldquosimplestrdquo possible sum type so this allows us to managetheir complexity much more than if we were directly writinga VOrd instance for a datatype with many constructors

Another interesting VOrd instance is that for K1 as theseare found at the leaves of a tree of pattern functor types

instance VOrd c rArr VOrd (K1 c) where

leqTransitive k k k Oh Oh =

case (k k k) of

(MkK1 x MkK1 y MkK1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

20

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

In terms of code this instance is quite simple as it simplyuses the underlying VOrd c instance to complete the proofof transitivity What may be less obvious is that the c typeno longer contains any pattern functor types as this is pointwhere we switch to the fields of the datatypersquos constructorsthemselves Put another way the generic computation ldquobot-toms outrdquo at occurrences of K1mdashor if a constructor has nofields it bottoms out at U1

In addition to (+) and K1 we prove the total order lawsfor the remaining two pattern functor types Once this isdone we obtain for free proofs that any Rep in a Genericinstance is a valid total ordering as we can compose thesefour instances as desired to obtain a valid VOrd instanceThis is crucial as it ensures that this technique scales up towhatever size of datatype that we might envision

35 Carrying Over ProofsGiven a VOrd instance for a representation type how canwe relate it back to the original datatype This is wherethe VGeneric class becomes important VGeneric gives usprecisely enough power to take a VOrd proof for one typeand reuse it for the other type in either direction

As a first example we demonstrate how to define a genericimplementation of leqTransitive for any type that imple-ments a VGeneric instance Intuitively we want to showthat a total order on a is transitive by appealing to the factthat the total order on Rep a (to which a is isomorphic) istransitive Computationally this amounts to taking each ofthe three arguments of type a converting them to their Repcounterparts with from and invoking leqTransitive onthem The general structure of this function looks like this

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ =

leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

This would almost typecheck save for one issue the final twoarguments we are attempting to pass to leqTransitive areof types So (x le y) and So (y le z) but since the firstthree arguments involve from this requires that the last twoarguments should actually have the types So (from x lefrom y) and So (from y le from z) respectivelyWhat goes wrong in this example The problem is that

while we attempt to appeal to the transitivity of representa-tion types the type signature does not reflect this A proofthat x le y for instance does not help much when the proofthat we actually care about involves from x and from y Inother words we need to find some way to relate the behaviorof (le) over type a to the behavior of (le) over type Rep aThere is a crude trick available that seemingly gets the

job done we can assume that the implementations of (le)

for a and Rep a coincide exactly That is to say instead ofusing (le) in the type signature of defaultLeqTransitivewhich is not precise enough we instead use genericLeqfrom Section 33 Recall that genericLeq x y = (from xle from y) which is exactly what we need here Thereforewe need only change the type of defaultLeqTransitiveslightly

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (genericLeq x y) rarr So (genericLeq y z)

rarr So (genericLeq x z)

This suffices to make defaultLeqTransitive typecheckbut it comes with a somewhat steep price as it requires that(le) be implemented exactly the same way as genericLeqWe will revisit this limitation in Section 4 and show how toovercome it For the remainder of this section it suffices toassume that (le) and genericLeq are definitionally equalWe use this trick to define default implementations of

the other laws in VOrd as well Using these defaults we canderive the total order laws much more easily than we couldbefore For example showing that T has a lawful total ordernow amount to very little code

instance VOrd a rArr VOrd (T a) where

leqTransitive = defaultLeqTransitive

The generic defaults for VOrd ended up not making useof any specific laws for VGeneric but this is not always thecase for every class A more complicated example is foundin Figure 2 which defines the Semigroup class for datatypessupporting binary associative operations In order to definea generic proof that this operation is associative we mustmake use of the fact that from to = id

defaultAssociative

Π (x y z a)

rarr (VGeneric a VSemigroup (Rep a))

rArr (genericAppend x (genericAppend y z)) sim

(genericAppend (genericAppend x y) z)

defaultAssociative x y z

| Refl larr ft (from x ltgt from y)

Refl larr ft (from y ltgt from z)

= associative (from x) (from y) (from z)

Using ft as a lemma guides the typechecker to realize thatthe conclusion reduces to (to (from x ltgt (from y ltgtfrom z))) sim (to ((from x ltgt from y) ltgt from z))From there appealing to the associativity of (ltgt) causes bothsides of the equation to become equal This example demon-strates that the VGeneric laws not only give us peace of mindthat the conversions to and from a representation type arestructure-preserving but that they are useful computationaltools for deriving generic proofs

21

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 4: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Updating leqReflexive ended up not requiring that mucheffort as we only needed to have one extra case for the addi-tional constructor MkT2 Updating leqTransitive howeverrequires more effort

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

In addition to requiring an extra case for the combination ofarguments where all of them are MkT2 we now require anadditional case to cover the two combinations of argumentswhere the first argument is MkT1 and the third argument isMkT2 The four other possible combinations of argumentswere ruled out by dependent pattern matching as the typesystem concluded that they were impossible-to-reach cases(for example if t = MkT2 _ and t = MkT1 _ then it can-not be the case that t le t is True) In Haskell we do notneed to write these cases out at all although some otherlanguages may require explicit ldquoabsurdrdquo cases (eg using the() pattern in Agda)

Having to add one more case might not seem that bur-densome but the number of cases one has to supply forleqTransitive grows quickly as we add more and moreconstructors For example if we added a third constructorMkT3 to T then after updating the Eq and Ord instances (us-ing the convention that MkT1 is always less than MkT2 andMkT2 is always less than MkT3) this is what the proof oftransitivity would become

instance VOrd a rArr VOrd (T a) where

leqTransitive t t t Oh Oh =

case (t t t) of

(MkT1 x MkT1 y MkT1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT2 x MkT2 y MkT2 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT3 x MkT3 y MkT3 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(MkT1 _ _ MkT2 _) = Oh

(MkT1 _ _ MkT3 _) = Oh

(MkT2 _ _ MkT3 _) = Oh

This time we have six cases If we add a fourth constructorwe would have 10 cases and if we add a fifth constructor wewould have fifteen cases In general if we had n constructorsthen leqTransitivewould require n+

(n2)= 1

2 (n2+n) cases

class Generic a where

type Rep a Type

from a rarr Rep a

to Rep a rarr a

data U1 = MkU1

newtype K1 c = MkK1 c

data a b = a b

data a + b = L1 a | R1 b

Figure 1A slightly simplified presentation of the definitionsfrom Haskellrsquos GHCGenerics library on which we base ourapproach to datatype-generic programming

which grows quadratically This has the potential to makescaling up proofs extremely tiresomePerhaps even more troublesome than the size of these

proofs themselves is the fact that most of these cases aresheer boilerplate For instance leqTransitive follows avery predictable pattern For combinations of argumentswhere all the constructors are the same recurse and forcombinations where there are different constructors it mustbe the case that the first argument is less than or equal to thethird argument so immediately return Oh This is routinecode that is begging to be automated with a proof-reusetechnique

3 Verified Instances GenericallyHaving seen the tedium of manually constructing certainproofs we present a solution Notably our solution does notrequire a tremendous amount of support from the languageitself (in terms of tactics or metaprogramming) Instead werely on techniques that could be ported to any dependentlytyped programming language that at a minimum supportstype classes2

We adapt an approach from the field of datatype-genericprogramming where we take an algebraic datatype and con-struct a representation type which is isomorphic to it Therepresentation type itself is a composition of smaller patternfunctor datatypes that encode primitive ldquobuilding blocksrdquo ofother datatypes such as products sums and individual fieldsWe also establish a type class for witnessing the isomorphismbetween a datatype and its representation type [17 20 36]By leveraging these tools we shift the burden of proof

from the original datatype (which may be arbitrarily com-plex) to the handful of simple pattern functor types thatconstitute its representation type This way we are able toprove properties for a wide range of possible datatypes bysimply proving the same properties for a finite number ofldquobuilding blockrdquo types

31 A Primer on Datatype-Generic ProgrammingTo build up representation types we build upon the tech-niques from Magalhatildees et al [17] which influenced the de-sign of Haskellrsquos popular GHCGenerics library Figure 12It is possible to further reduce the amount of code needed by generat-ing boilerplate code with metaprogramming techniques (eg TemplateHaskell [24]) but support for metaprogramming is not a strict requirement

18

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

presents the central definitions used in this style of datatype-generic programmingThe Generic type class is the focal point of the library

A datatype with a Generic instance comes equipped witha Rep the representation type and functions from and towhich witness the isomorphism between the representationtype and the original datatype The Rep type is always somecombination of the following pattern functor types3

1 The U1 type represents constructors with no fields2 The K1 type represents a single field in a constructor3 The () type (pronounced ldquoproductrdquo) represents

two consecutive fields in a constructor4 The (+) type (pronounced ldquosumrdquo) represents the

choice between two consecutive constructorsThis is best explained with an example so recall this ver-

sion of the T data type from Section 212

data T a = MkT1 a | MkT2 a

We define its canonical Generic instance like soinstance Generic (T a) where

type Rep (T a) = K1 a + K1 a

from (MkT1 x) = L1 (MkK1 x)

from (MkT2 x) = R1 (MkK1 x)

to (L1 (MkK1 x)) = MkT1 x

to (R1 (MkK1 x)) = MkT2 x

Here we see that because T has two constructors MkT1 andMkT2 the (+) type is used once to represent the choicebetween them The field of type a in each constructor is inturn represented with a K1 type The same sort of patternwould follow if we added more constructors to T For exam-ple here is how the Rep instance would appear if there werethree constructors 4

data T a = MkT1 a | MkT2 a | MkT3 Bool

instance Generic (T a) where

type Rep (T a) = K1 a + (K1 a + K1 Bool)

Each constructor corresponds to another use of (+) todenote another choice of constructor Despite the size of theT type increasing the number of distinct datatypes in its Rephas not changed which is an important property

The implementations of from and to are are entirely me-chanical to implement and constitute one of the few sourcesof boilerplate in this style of datatype-generic programmingLanguages like Haskell offer a metaprogramming facility(deriving Generic) for generating this boilerplate althoughit can just as well be written by hand3There is another pattern functor type M1 which is used to attach metadatasuch as constructor names fixities etc For the sake of simplicity we leaveit out of the discussion in this section but we revisit it in 514Although (+) is right-associative we have added explicit parenthesesin this Rep instance for clarity In general a canonical Generic instancebalances nested uses of (+) and () so that it is always possible togo from the root of the representation type to a leaf in logarithmic (ratherthan linear) time

32 Verifying Generic

While Generic is convenient for quickly coming up withrepresentation types it alone isnrsquot enough for our needsas we need to be able to prove that from and to form anisomorphism In pursuit of that goal we define a subclass ofGeneric with two proof methods that state that from andto are mutual inverses

class Generic a rArr VGeneric a where

tf Π (z a) rarr to (from z) sim z

ft Π (r Rep a) rarr from (to r) sim r

Like Generic instances of VGeneric are predictably straight-forward Here is an example of instance for T (with twoconstructors)

instance VGeneric (T a) where

tf (MkT1 _) = Refl

tf (MkT2 _) = Refl

ft (L1 (MkK1 _)) = Refl

ft (R1 (MkK1 _)) = Refl

The implementations of tf and ft are the other sourcesof boilerplate besides from and to For this reason we exposeTemplateHaskell [24] functionality in the verified-classeslibrary to automatically generate VGeneric instances

The class VGeneric plays double duty in the style of proofswe write One of its roles is to serve as a tool for ldquocancellingoutrdquo compositions of from and to as the need often arises tosimplify to (from z) into z or from (to r) into r whenreasoning about generic implementations of class methods Italso serves the role of ensuring that the Generic instance weare using to go between a datatype and its Rep is a legitimateisomorphism Even if the Generic instance we are using isgenerated behind the scenes (say with deriving Generic)we can use VGeneric as an additional sanity check to ensurethat the Generic automation is functioning properly

33 Orderings on Representation TypesWe have now identified the four basic datatypes in Figure 1that can be composed in various ways to form representa-tion types in Generic instances as well as a mechanism toverify that the choice of representation type truly forms anisomorphism The next step is to utilize these tools to writeverified Ord instances and by doing so demonstrate howto obtain a valid total ordering for any algebraic data typeusing this technique

First we define a generic version of (le) by defining Ordinstances for the pattern functor types The instances for U1and K1 are quite straightforward

instance Ord U1 where

MkU1 le MkU1 = True

instance Ord c rArr Ord (K1 c) where

(MkK1 x) le (MkK1 y) = (x le y)

19

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

It is worthwhile to take the time to reflect on what these in-stances mean from a datatype-generic programming perspec-tive The instance for U1 abstracts the idea that a constructorwith no fields is always less than or equal to itself and theinstance for K1 abstracts the idea that for constructors withfields the comparing them amounts to comparing the con-stituent fields The idea of ldquocomparing the constituent fieldsrdquobecomes more precise when we define the Ord instance for()

instance (Ord a Ord b) rArr Ord (a b) where

(x1 y1) le (x2 y2) =

if (x1 == x2) then (y1 le y2) else (x1 le x2)

This instance abstracts the idea that we use a lexicographicordering on products That is we check the first fields ofeach constructor and if they are the same skip them andproceed to the next fields If they are not the same returnthe result of comparing them Importantly this approachscales to any number of fields as this instance iterates overnested uses of () to process the remaining fields

Finally we encode how to compare multiple constructorswith (+)

instance (Ord a Ord b) rArr Ord (a + b) where

(L1 x) le (L1 y) = (x le y)

(R1 x) le (R1 y) = (x le y)

(L1 _) le (R1 _) = True

(R1 _) le (L1 _) = False

This instance abstracts the convention that constructors de-fined earlier in a datatypersquos definition are always less thanconstructors defined later Moreover comparing two of thesame constructor amounts to comparing their respectivefields

Defining these four Ord instances for the pattern functortypes means that any datatype equipped with a Genericinstance can derive an Ord instance cheaply This is becauseit is possible to define an implementation of (le) which wecall genericLeq that works for any instance of Generic

genericLeq (Generic a Ord (Rep a))

rArr a rarr a rarr Bool

genericLeq x y = (from x le from y)

instance Ord a rArr Ord (T a) where

(le) = genericLeq

This works because we can define an order over a datatype interms of the ordering on its representation type to which itis isomorphic Previously we would have had to implementOrd once per datatype with each implementation possiblyrequiring several casesWith datatype-generic programmingwe can reduce the implementation burden to defining (le)for each pattern functor (a one-time cost) defining a Genericinstance per datatype (which are simple and can be auto-mated) and defining an Ord instance per datatype usinggenericLeq (which only requires one line per type)

It is worth clarifying that genericLeq is ldquogenericrdquo in thesense that it provides a canonical implementation of a totalordering on datatypes It is canonical in the sense that itis law-abiding and works for a wide variety of datatypeseven if they have different numbers of constructors or fieldsThat is not to say that this is the only valid total order inexistence For instance we could choose a reverse lexico-graphic ordering that treats rightmost constructors as alwaysbeing less than leftmost constructors We could certainlyaccommodate datatypes with this ordering by inventing aReverseLexicoOrd class and defining appropriate instancesof it for the pattern functor types but in general there mightbe arbitrarily many law-abiding implementations of a classrsquosmethodsIn this work we are primarily concerned with canoni-

cal implementations of class methods as they reflect ldquooff-the-shelfrdquo solutions that programmers reach for when theywant to define instances for their data types in a cheap-and-cheerful manner Therefore we restrict our focus to onegeneric default per class method even though many morelegitimate defaults may exist

34 Proofs over Representation TypesJust as we defined a generic version of (le) over patternfunctors so too can we define generic versions of the totalorder laws by defining VOrd instances Here is a subset ofthe VOrd instance for sums

instance (VOrd a VOrd b) rArr VOrd (a + b) where

leqTransitive s s s Oh Oh =

case (s s s) of

(L1 x L1 y L1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(R1 x R1 y R1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(L1 _ _ R1 _) = Oh

Astute readers will notice that this is the exact same as aVOrd instance that we defined in Section 212 Howevernote that this is the only instance of VOrd that we need toprovide for a sum type Moreover (+) is in some waysthe ldquosimplestrdquo possible sum type so this allows us to managetheir complexity much more than if we were directly writinga VOrd instance for a datatype with many constructors

Another interesting VOrd instance is that for K1 as theseare found at the leaves of a tree of pattern functor types

instance VOrd c rArr VOrd (K1 c) where

leqTransitive k k k Oh Oh =

case (k k k) of

(MkK1 x MkK1 y MkK1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

20

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

In terms of code this instance is quite simple as it simplyuses the underlying VOrd c instance to complete the proofof transitivity What may be less obvious is that the c typeno longer contains any pattern functor types as this is pointwhere we switch to the fields of the datatypersquos constructorsthemselves Put another way the generic computation ldquobot-toms outrdquo at occurrences of K1mdashor if a constructor has nofields it bottoms out at U1

In addition to (+) and K1 we prove the total order lawsfor the remaining two pattern functor types Once this isdone we obtain for free proofs that any Rep in a Genericinstance is a valid total ordering as we can compose thesefour instances as desired to obtain a valid VOrd instanceThis is crucial as it ensures that this technique scales up towhatever size of datatype that we might envision

35 Carrying Over ProofsGiven a VOrd instance for a representation type how canwe relate it back to the original datatype This is wherethe VGeneric class becomes important VGeneric gives usprecisely enough power to take a VOrd proof for one typeand reuse it for the other type in either direction

As a first example we demonstrate how to define a genericimplementation of leqTransitive for any type that imple-ments a VGeneric instance Intuitively we want to showthat a total order on a is transitive by appealing to the factthat the total order on Rep a (to which a is isomorphic) istransitive Computationally this amounts to taking each ofthe three arguments of type a converting them to their Repcounterparts with from and invoking leqTransitive onthem The general structure of this function looks like this

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ =

leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

This would almost typecheck save for one issue the final twoarguments we are attempting to pass to leqTransitive areof types So (x le y) and So (y le z) but since the firstthree arguments involve from this requires that the last twoarguments should actually have the types So (from x lefrom y) and So (from y le from z) respectivelyWhat goes wrong in this example The problem is that

while we attempt to appeal to the transitivity of representa-tion types the type signature does not reflect this A proofthat x le y for instance does not help much when the proofthat we actually care about involves from x and from y Inother words we need to find some way to relate the behaviorof (le) over type a to the behavior of (le) over type Rep aThere is a crude trick available that seemingly gets the

job done we can assume that the implementations of (le)

for a and Rep a coincide exactly That is to say instead ofusing (le) in the type signature of defaultLeqTransitivewhich is not precise enough we instead use genericLeqfrom Section 33 Recall that genericLeq x y = (from xle from y) which is exactly what we need here Thereforewe need only change the type of defaultLeqTransitiveslightly

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (genericLeq x y) rarr So (genericLeq y z)

rarr So (genericLeq x z)

This suffices to make defaultLeqTransitive typecheckbut it comes with a somewhat steep price as it requires that(le) be implemented exactly the same way as genericLeqWe will revisit this limitation in Section 4 and show how toovercome it For the remainder of this section it suffices toassume that (le) and genericLeq are definitionally equalWe use this trick to define default implementations of

the other laws in VOrd as well Using these defaults we canderive the total order laws much more easily than we couldbefore For example showing that T has a lawful total ordernow amount to very little code

instance VOrd a rArr VOrd (T a) where

leqTransitive = defaultLeqTransitive

The generic defaults for VOrd ended up not making useof any specific laws for VGeneric but this is not always thecase for every class A more complicated example is foundin Figure 2 which defines the Semigroup class for datatypessupporting binary associative operations In order to definea generic proof that this operation is associative we mustmake use of the fact that from to = id

defaultAssociative

Π (x y z a)

rarr (VGeneric a VSemigroup (Rep a))

rArr (genericAppend x (genericAppend y z)) sim

(genericAppend (genericAppend x y) z)

defaultAssociative x y z

| Refl larr ft (from x ltgt from y)

Refl larr ft (from y ltgt from z)

= associative (from x) (from y) (from z)

Using ft as a lemma guides the typechecker to realize thatthe conclusion reduces to (to (from x ltgt (from y ltgtfrom z))) sim (to ((from x ltgt from y) ltgt from z))From there appealing to the associativity of (ltgt) causes bothsides of the equation to become equal This example demon-strates that the VGeneric laws not only give us peace of mindthat the conversions to and from a representation type arestructure-preserving but that they are useful computationaltools for deriving generic proofs

21

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 5: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

presents the central definitions used in this style of datatype-generic programmingThe Generic type class is the focal point of the library

A datatype with a Generic instance comes equipped witha Rep the representation type and functions from and towhich witness the isomorphism between the representationtype and the original datatype The Rep type is always somecombination of the following pattern functor types3

1 The U1 type represents constructors with no fields2 The K1 type represents a single field in a constructor3 The () type (pronounced ldquoproductrdquo) represents

two consecutive fields in a constructor4 The (+) type (pronounced ldquosumrdquo) represents the

choice between two consecutive constructorsThis is best explained with an example so recall this ver-

sion of the T data type from Section 212

data T a = MkT1 a | MkT2 a

We define its canonical Generic instance like soinstance Generic (T a) where

type Rep (T a) = K1 a + K1 a

from (MkT1 x) = L1 (MkK1 x)

from (MkT2 x) = R1 (MkK1 x)

to (L1 (MkK1 x)) = MkT1 x

to (R1 (MkK1 x)) = MkT2 x

Here we see that because T has two constructors MkT1 andMkT2 the (+) type is used once to represent the choicebetween them The field of type a in each constructor is inturn represented with a K1 type The same sort of patternwould follow if we added more constructors to T For exam-ple here is how the Rep instance would appear if there werethree constructors 4

data T a = MkT1 a | MkT2 a | MkT3 Bool

instance Generic (T a) where

type Rep (T a) = K1 a + (K1 a + K1 Bool)

Each constructor corresponds to another use of (+) todenote another choice of constructor Despite the size of theT type increasing the number of distinct datatypes in its Rephas not changed which is an important property

The implementations of from and to are are entirely me-chanical to implement and constitute one of the few sourcesof boilerplate in this style of datatype-generic programmingLanguages like Haskell offer a metaprogramming facility(deriving Generic) for generating this boilerplate althoughit can just as well be written by hand3There is another pattern functor type M1 which is used to attach metadatasuch as constructor names fixities etc For the sake of simplicity we leaveit out of the discussion in this section but we revisit it in 514Although (+) is right-associative we have added explicit parenthesesin this Rep instance for clarity In general a canonical Generic instancebalances nested uses of (+) and () so that it is always possible togo from the root of the representation type to a leaf in logarithmic (ratherthan linear) time

32 Verifying Generic

While Generic is convenient for quickly coming up withrepresentation types it alone isnrsquot enough for our needsas we need to be able to prove that from and to form anisomorphism In pursuit of that goal we define a subclass ofGeneric with two proof methods that state that from andto are mutual inverses

class Generic a rArr VGeneric a where

tf Π (z a) rarr to (from z) sim z

ft Π (r Rep a) rarr from (to r) sim r

Like Generic instances of VGeneric are predictably straight-forward Here is an example of instance for T (with twoconstructors)

instance VGeneric (T a) where

tf (MkT1 _) = Refl

tf (MkT2 _) = Refl

ft (L1 (MkK1 _)) = Refl

ft (R1 (MkK1 _)) = Refl

The implementations of tf and ft are the other sourcesof boilerplate besides from and to For this reason we exposeTemplateHaskell [24] functionality in the verified-classeslibrary to automatically generate VGeneric instances

The class VGeneric plays double duty in the style of proofswe write One of its roles is to serve as a tool for ldquocancellingoutrdquo compositions of from and to as the need often arises tosimplify to (from z) into z or from (to r) into r whenreasoning about generic implementations of class methods Italso serves the role of ensuring that the Generic instance weare using to go between a datatype and its Rep is a legitimateisomorphism Even if the Generic instance we are using isgenerated behind the scenes (say with deriving Generic)we can use VGeneric as an additional sanity check to ensurethat the Generic automation is functioning properly

33 Orderings on Representation TypesWe have now identified the four basic datatypes in Figure 1that can be composed in various ways to form representa-tion types in Generic instances as well as a mechanism toverify that the choice of representation type truly forms anisomorphism The next step is to utilize these tools to writeverified Ord instances and by doing so demonstrate howto obtain a valid total ordering for any algebraic data typeusing this technique

First we define a generic version of (le) by defining Ordinstances for the pattern functor types The instances for U1and K1 are quite straightforward

instance Ord U1 where

MkU1 le MkU1 = True

instance Ord c rArr Ord (K1 c) where

(MkK1 x) le (MkK1 y) = (x le y)

19

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

It is worthwhile to take the time to reflect on what these in-stances mean from a datatype-generic programming perspec-tive The instance for U1 abstracts the idea that a constructorwith no fields is always less than or equal to itself and theinstance for K1 abstracts the idea that for constructors withfields the comparing them amounts to comparing the con-stituent fields The idea of ldquocomparing the constituent fieldsrdquobecomes more precise when we define the Ord instance for()

instance (Ord a Ord b) rArr Ord (a b) where

(x1 y1) le (x2 y2) =

if (x1 == x2) then (y1 le y2) else (x1 le x2)

This instance abstracts the idea that we use a lexicographicordering on products That is we check the first fields ofeach constructor and if they are the same skip them andproceed to the next fields If they are not the same returnthe result of comparing them Importantly this approachscales to any number of fields as this instance iterates overnested uses of () to process the remaining fields

Finally we encode how to compare multiple constructorswith (+)

instance (Ord a Ord b) rArr Ord (a + b) where

(L1 x) le (L1 y) = (x le y)

(R1 x) le (R1 y) = (x le y)

(L1 _) le (R1 _) = True

(R1 _) le (L1 _) = False

This instance abstracts the convention that constructors de-fined earlier in a datatypersquos definition are always less thanconstructors defined later Moreover comparing two of thesame constructor amounts to comparing their respectivefields

Defining these four Ord instances for the pattern functortypes means that any datatype equipped with a Genericinstance can derive an Ord instance cheaply This is becauseit is possible to define an implementation of (le) which wecall genericLeq that works for any instance of Generic

genericLeq (Generic a Ord (Rep a))

rArr a rarr a rarr Bool

genericLeq x y = (from x le from y)

instance Ord a rArr Ord (T a) where

(le) = genericLeq

This works because we can define an order over a datatype interms of the ordering on its representation type to which itis isomorphic Previously we would have had to implementOrd once per datatype with each implementation possiblyrequiring several casesWith datatype-generic programmingwe can reduce the implementation burden to defining (le)for each pattern functor (a one-time cost) defining a Genericinstance per datatype (which are simple and can be auto-mated) and defining an Ord instance per datatype usinggenericLeq (which only requires one line per type)

It is worth clarifying that genericLeq is ldquogenericrdquo in thesense that it provides a canonical implementation of a totalordering on datatypes It is canonical in the sense that itis law-abiding and works for a wide variety of datatypeseven if they have different numbers of constructors or fieldsThat is not to say that this is the only valid total order inexistence For instance we could choose a reverse lexico-graphic ordering that treats rightmost constructors as alwaysbeing less than leftmost constructors We could certainlyaccommodate datatypes with this ordering by inventing aReverseLexicoOrd class and defining appropriate instancesof it for the pattern functor types but in general there mightbe arbitrarily many law-abiding implementations of a classrsquosmethodsIn this work we are primarily concerned with canoni-

cal implementations of class methods as they reflect ldquooff-the-shelfrdquo solutions that programmers reach for when theywant to define instances for their data types in a cheap-and-cheerful manner Therefore we restrict our focus to onegeneric default per class method even though many morelegitimate defaults may exist

34 Proofs over Representation TypesJust as we defined a generic version of (le) over patternfunctors so too can we define generic versions of the totalorder laws by defining VOrd instances Here is a subset ofthe VOrd instance for sums

instance (VOrd a VOrd b) rArr VOrd (a + b) where

leqTransitive s s s Oh Oh =

case (s s s) of

(L1 x L1 y L1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(R1 x R1 y R1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(L1 _ _ R1 _) = Oh

Astute readers will notice that this is the exact same as aVOrd instance that we defined in Section 212 Howevernote that this is the only instance of VOrd that we need toprovide for a sum type Moreover (+) is in some waysthe ldquosimplestrdquo possible sum type so this allows us to managetheir complexity much more than if we were directly writinga VOrd instance for a datatype with many constructors

Another interesting VOrd instance is that for K1 as theseare found at the leaves of a tree of pattern functor types

instance VOrd c rArr VOrd (K1 c) where

leqTransitive k k k Oh Oh =

case (k k k) of

(MkK1 x MkK1 y MkK1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

20

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

In terms of code this instance is quite simple as it simplyuses the underlying VOrd c instance to complete the proofof transitivity What may be less obvious is that the c typeno longer contains any pattern functor types as this is pointwhere we switch to the fields of the datatypersquos constructorsthemselves Put another way the generic computation ldquobot-toms outrdquo at occurrences of K1mdashor if a constructor has nofields it bottoms out at U1

In addition to (+) and K1 we prove the total order lawsfor the remaining two pattern functor types Once this isdone we obtain for free proofs that any Rep in a Genericinstance is a valid total ordering as we can compose thesefour instances as desired to obtain a valid VOrd instanceThis is crucial as it ensures that this technique scales up towhatever size of datatype that we might envision

35 Carrying Over ProofsGiven a VOrd instance for a representation type how canwe relate it back to the original datatype This is wherethe VGeneric class becomes important VGeneric gives usprecisely enough power to take a VOrd proof for one typeand reuse it for the other type in either direction

As a first example we demonstrate how to define a genericimplementation of leqTransitive for any type that imple-ments a VGeneric instance Intuitively we want to showthat a total order on a is transitive by appealing to the factthat the total order on Rep a (to which a is isomorphic) istransitive Computationally this amounts to taking each ofthe three arguments of type a converting them to their Repcounterparts with from and invoking leqTransitive onthem The general structure of this function looks like this

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ =

leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

This would almost typecheck save for one issue the final twoarguments we are attempting to pass to leqTransitive areof types So (x le y) and So (y le z) but since the firstthree arguments involve from this requires that the last twoarguments should actually have the types So (from x lefrom y) and So (from y le from z) respectivelyWhat goes wrong in this example The problem is that

while we attempt to appeal to the transitivity of representa-tion types the type signature does not reflect this A proofthat x le y for instance does not help much when the proofthat we actually care about involves from x and from y Inother words we need to find some way to relate the behaviorof (le) over type a to the behavior of (le) over type Rep aThere is a crude trick available that seemingly gets the

job done we can assume that the implementations of (le)

for a and Rep a coincide exactly That is to say instead ofusing (le) in the type signature of defaultLeqTransitivewhich is not precise enough we instead use genericLeqfrom Section 33 Recall that genericLeq x y = (from xle from y) which is exactly what we need here Thereforewe need only change the type of defaultLeqTransitiveslightly

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (genericLeq x y) rarr So (genericLeq y z)

rarr So (genericLeq x z)

This suffices to make defaultLeqTransitive typecheckbut it comes with a somewhat steep price as it requires that(le) be implemented exactly the same way as genericLeqWe will revisit this limitation in Section 4 and show how toovercome it For the remainder of this section it suffices toassume that (le) and genericLeq are definitionally equalWe use this trick to define default implementations of

the other laws in VOrd as well Using these defaults we canderive the total order laws much more easily than we couldbefore For example showing that T has a lawful total ordernow amount to very little code

instance VOrd a rArr VOrd (T a) where

leqTransitive = defaultLeqTransitive

The generic defaults for VOrd ended up not making useof any specific laws for VGeneric but this is not always thecase for every class A more complicated example is foundin Figure 2 which defines the Semigroup class for datatypessupporting binary associative operations In order to definea generic proof that this operation is associative we mustmake use of the fact that from to = id

defaultAssociative

Π (x y z a)

rarr (VGeneric a VSemigroup (Rep a))

rArr (genericAppend x (genericAppend y z)) sim

(genericAppend (genericAppend x y) z)

defaultAssociative x y z

| Refl larr ft (from x ltgt from y)

Refl larr ft (from y ltgt from z)

= associative (from x) (from y) (from z)

Using ft as a lemma guides the typechecker to realize thatthe conclusion reduces to (to (from x ltgt (from y ltgtfrom z))) sim (to ((from x ltgt from y) ltgt from z))From there appealing to the associativity of (ltgt) causes bothsides of the equation to become equal This example demon-strates that the VGeneric laws not only give us peace of mindthat the conversions to and from a representation type arestructure-preserving but that they are useful computationaltools for deriving generic proofs

21

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 6: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

It is worthwhile to take the time to reflect on what these in-stances mean from a datatype-generic programming perspec-tive The instance for U1 abstracts the idea that a constructorwith no fields is always less than or equal to itself and theinstance for K1 abstracts the idea that for constructors withfields the comparing them amounts to comparing the con-stituent fields The idea of ldquocomparing the constituent fieldsrdquobecomes more precise when we define the Ord instance for()

instance (Ord a Ord b) rArr Ord (a b) where

(x1 y1) le (x2 y2) =

if (x1 == x2) then (y1 le y2) else (x1 le x2)

This instance abstracts the idea that we use a lexicographicordering on products That is we check the first fields ofeach constructor and if they are the same skip them andproceed to the next fields If they are not the same returnthe result of comparing them Importantly this approachscales to any number of fields as this instance iterates overnested uses of () to process the remaining fields

Finally we encode how to compare multiple constructorswith (+)

instance (Ord a Ord b) rArr Ord (a + b) where

(L1 x) le (L1 y) = (x le y)

(R1 x) le (R1 y) = (x le y)

(L1 _) le (R1 _) = True

(R1 _) le (L1 _) = False

This instance abstracts the convention that constructors de-fined earlier in a datatypersquos definition are always less thanconstructors defined later Moreover comparing two of thesame constructor amounts to comparing their respectivefields

Defining these four Ord instances for the pattern functortypes means that any datatype equipped with a Genericinstance can derive an Ord instance cheaply This is becauseit is possible to define an implementation of (le) which wecall genericLeq that works for any instance of Generic

genericLeq (Generic a Ord (Rep a))

rArr a rarr a rarr Bool

genericLeq x y = (from x le from y)

instance Ord a rArr Ord (T a) where

(le) = genericLeq

This works because we can define an order over a datatype interms of the ordering on its representation type to which itis isomorphic Previously we would have had to implementOrd once per datatype with each implementation possiblyrequiring several casesWith datatype-generic programmingwe can reduce the implementation burden to defining (le)for each pattern functor (a one-time cost) defining a Genericinstance per datatype (which are simple and can be auto-mated) and defining an Ord instance per datatype usinggenericLeq (which only requires one line per type)

It is worth clarifying that genericLeq is ldquogenericrdquo in thesense that it provides a canonical implementation of a totalordering on datatypes It is canonical in the sense that itis law-abiding and works for a wide variety of datatypeseven if they have different numbers of constructors or fieldsThat is not to say that this is the only valid total order inexistence For instance we could choose a reverse lexico-graphic ordering that treats rightmost constructors as alwaysbeing less than leftmost constructors We could certainlyaccommodate datatypes with this ordering by inventing aReverseLexicoOrd class and defining appropriate instancesof it for the pattern functor types but in general there mightbe arbitrarily many law-abiding implementations of a classrsquosmethodsIn this work we are primarily concerned with canoni-

cal implementations of class methods as they reflect ldquooff-the-shelfrdquo solutions that programmers reach for when theywant to define instances for their data types in a cheap-and-cheerful manner Therefore we restrict our focus to onegeneric default per class method even though many morelegitimate defaults may exist

34 Proofs over Representation TypesJust as we defined a generic version of (le) over patternfunctors so too can we define generic versions of the totalorder laws by defining VOrd instances Here is a subset ofthe VOrd instance for sums

instance (VOrd a VOrd b) rArr VOrd (a + b) where

leqTransitive s s s Oh Oh =

case (s s s) of

(L1 x L1 y L1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(R1 x R1 y R1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

(L1 _ _ R1 _) = Oh

Astute readers will notice that this is the exact same as aVOrd instance that we defined in Section 212 Howevernote that this is the only instance of VOrd that we need toprovide for a sum type Moreover (+) is in some waysthe ldquosimplestrdquo possible sum type so this allows us to managetheir complexity much more than if we were directly writinga VOrd instance for a datatype with many constructors

Another interesting VOrd instance is that for K1 as theseare found at the leaves of a tree of pattern functor types

instance VOrd c rArr VOrd (K1 c) where

leqTransitive k k k Oh Oh =

case (k k k) of

(MkK1 x MkK1 y MkK1 z)

| Oh larr leqTransitive x y z Oh Oh = Oh

20

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

In terms of code this instance is quite simple as it simplyuses the underlying VOrd c instance to complete the proofof transitivity What may be less obvious is that the c typeno longer contains any pattern functor types as this is pointwhere we switch to the fields of the datatypersquos constructorsthemselves Put another way the generic computation ldquobot-toms outrdquo at occurrences of K1mdashor if a constructor has nofields it bottoms out at U1

In addition to (+) and K1 we prove the total order lawsfor the remaining two pattern functor types Once this isdone we obtain for free proofs that any Rep in a Genericinstance is a valid total ordering as we can compose thesefour instances as desired to obtain a valid VOrd instanceThis is crucial as it ensures that this technique scales up towhatever size of datatype that we might envision

35 Carrying Over ProofsGiven a VOrd instance for a representation type how canwe relate it back to the original datatype This is wherethe VGeneric class becomes important VGeneric gives usprecisely enough power to take a VOrd proof for one typeand reuse it for the other type in either direction

As a first example we demonstrate how to define a genericimplementation of leqTransitive for any type that imple-ments a VGeneric instance Intuitively we want to showthat a total order on a is transitive by appealing to the factthat the total order on Rep a (to which a is isomorphic) istransitive Computationally this amounts to taking each ofthe three arguments of type a converting them to their Repcounterparts with from and invoking leqTransitive onthem The general structure of this function looks like this

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ =

leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

This would almost typecheck save for one issue the final twoarguments we are attempting to pass to leqTransitive areof types So (x le y) and So (y le z) but since the firstthree arguments involve from this requires that the last twoarguments should actually have the types So (from x lefrom y) and So (from y le from z) respectivelyWhat goes wrong in this example The problem is that

while we attempt to appeal to the transitivity of representa-tion types the type signature does not reflect this A proofthat x le y for instance does not help much when the proofthat we actually care about involves from x and from y Inother words we need to find some way to relate the behaviorof (le) over type a to the behavior of (le) over type Rep aThere is a crude trick available that seemingly gets the

job done we can assume that the implementations of (le)

for a and Rep a coincide exactly That is to say instead ofusing (le) in the type signature of defaultLeqTransitivewhich is not precise enough we instead use genericLeqfrom Section 33 Recall that genericLeq x y = (from xle from y) which is exactly what we need here Thereforewe need only change the type of defaultLeqTransitiveslightly

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (genericLeq x y) rarr So (genericLeq y z)

rarr So (genericLeq x z)

This suffices to make defaultLeqTransitive typecheckbut it comes with a somewhat steep price as it requires that(le) be implemented exactly the same way as genericLeqWe will revisit this limitation in Section 4 and show how toovercome it For the remainder of this section it suffices toassume that (le) and genericLeq are definitionally equalWe use this trick to define default implementations of

the other laws in VOrd as well Using these defaults we canderive the total order laws much more easily than we couldbefore For example showing that T has a lawful total ordernow amount to very little code

instance VOrd a rArr VOrd (T a) where

leqTransitive = defaultLeqTransitive

The generic defaults for VOrd ended up not making useof any specific laws for VGeneric but this is not always thecase for every class A more complicated example is foundin Figure 2 which defines the Semigroup class for datatypessupporting binary associative operations In order to definea generic proof that this operation is associative we mustmake use of the fact that from to = id

defaultAssociative

Π (x y z a)

rarr (VGeneric a VSemigroup (Rep a))

rArr (genericAppend x (genericAppend y z)) sim

(genericAppend (genericAppend x y) z)

defaultAssociative x y z

| Refl larr ft (from x ltgt from y)

Refl larr ft (from y ltgt from z)

= associative (from x) (from y) (from z)

Using ft as a lemma guides the typechecker to realize thatthe conclusion reduces to (to (from x ltgt (from y ltgtfrom z))) sim (to ((from x ltgt from y) ltgt from z))From there appealing to the associativity of (ltgt) causes bothsides of the equation to become equal This example demon-strates that the VGeneric laws not only give us peace of mindthat the conversions to and from a representation type arestructure-preserving but that they are useful computationaltools for deriving generic proofs

21

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 7: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

In terms of code this instance is quite simple as it simplyuses the underlying VOrd c instance to complete the proofof transitivity What may be less obvious is that the c typeno longer contains any pattern functor types as this is pointwhere we switch to the fields of the datatypersquos constructorsthemselves Put another way the generic computation ldquobot-toms outrdquo at occurrences of K1mdashor if a constructor has nofields it bottoms out at U1

In addition to (+) and K1 we prove the total order lawsfor the remaining two pattern functor types Once this isdone we obtain for free proofs that any Rep in a Genericinstance is a valid total ordering as we can compose thesefour instances as desired to obtain a valid VOrd instanceThis is crucial as it ensures that this technique scales up towhatever size of datatype that we might envision

35 Carrying Over ProofsGiven a VOrd instance for a representation type how canwe relate it back to the original datatype This is wherethe VGeneric class becomes important VGeneric gives usprecisely enough power to take a VOrd proof for one typeand reuse it for the other type in either direction

As a first example we demonstrate how to define a genericimplementation of leqTransitive for any type that imple-ments a VGeneric instance Intuitively we want to showthat a total order on a is transitive by appealing to the factthat the total order on Rep a (to which a is isomorphic) istransitive Computationally this amounts to taking each ofthe three arguments of type a converting them to their Repcounterparts with from and invoking leqTransitive onthem The general structure of this function looks like this

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ =

leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

This would almost typecheck save for one issue the final twoarguments we are attempting to pass to leqTransitive areof types So (x le y) and So (y le z) but since the firstthree arguments involve from this requires that the last twoarguments should actually have the types So (from x lefrom y) and So (from y le from z) respectivelyWhat goes wrong in this example The problem is that

while we attempt to appeal to the transitivity of representa-tion types the type signature does not reflect this A proofthat x le y for instance does not help much when the proofthat we actually care about involves from x and from y Inother words we need to find some way to relate the behaviorof (le) over type a to the behavior of (le) over type Rep aThere is a crude trick available that seemingly gets the

job done we can assume that the implementations of (le)

for a and Rep a coincide exactly That is to say instead ofusing (le) in the type signature of defaultLeqTransitivewhich is not precise enough we instead use genericLeqfrom Section 33 Recall that genericLeq x y = (from xle from y) which is exactly what we need here Thereforewe need only change the type of defaultLeqTransitiveslightly

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a))

rArr So (genericLeq x y) rarr So (genericLeq y z)

rarr So (genericLeq x z)

This suffices to make defaultLeqTransitive typecheckbut it comes with a somewhat steep price as it requires that(le) be implemented exactly the same way as genericLeqWe will revisit this limitation in Section 4 and show how toovercome it For the remainder of this section it suffices toassume that (le) and genericLeq are definitionally equalWe use this trick to define default implementations of

the other laws in VOrd as well Using these defaults we canderive the total order laws much more easily than we couldbefore For example showing that T has a lawful total ordernow amount to very little code

instance VOrd a rArr VOrd (T a) where

leqTransitive = defaultLeqTransitive

The generic defaults for VOrd ended up not making useof any specific laws for VGeneric but this is not always thecase for every class A more complicated example is foundin Figure 2 which defines the Semigroup class for datatypessupporting binary associative operations In order to definea generic proof that this operation is associative we mustmake use of the fact that from to = id

defaultAssociative

Π (x y z a)

rarr (VGeneric a VSemigroup (Rep a))

rArr (genericAppend x (genericAppend y z)) sim

(genericAppend (genericAppend x y) z)

defaultAssociative x y z

| Refl larr ft (from x ltgt from y)

Refl larr ft (from y ltgt from z)

= associative (from x) (from y) (from z)

Using ft as a lemma guides the typechecker to realize thatthe conclusion reduces to (to (from x ltgt (from y ltgtfrom z))) sim (to ((from x ltgt from y) ltgt from z))From there appealing to the associativity of (ltgt) causes bothsides of the equation to become equal This example demon-strates that the VGeneric laws not only give us peace of mindthat the conversions to and from a representation type arestructure-preserving but that they are useful computationaltools for deriving generic proofs

21

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 8: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

class Semigroup a where

(ltgt) a rarr a rarr a

class Semigroup a rArr VSemigroup a where

associative Π (x y z a)

rarr (x ltgt (y ltgt z)) sim ((x ltgt y) ltgt z)

genericAppend

(Generic a Semigroup (Rep a))

rArr a rarr a rarr a

genericAppend x y =

to (from x ltgt from y)

Figure 2 The Semigroup class its associativity law (in VSemigroup) and a generic implementation of (ltgt)

4 More Permissive Generic DefaultsIn Section 3 we demonstrated how datatype-generic pro-gramming could derive verified proofs of type class laws inaddition to the implementations of the class methods them-selves But the utility of these proofs is still somewhat lim-ited When defining generic defaults for laws we assumed(in Section 34) that the implementation of the class meth-ods involved were exactly the same as the datatype-genericversions While this worked out well enough for the oneOrdVOrd example we used in Section 35 this approach hasa serious flaw it cannot work if the implementation of (le)is anything other than genericLeq As a consequence thereare large swaths of Ord instances in the wild that cannot ben-efit from generic proofs since their Ord instances werenrsquotdesigned to accommodate itWith a little modification however we can make the

generic proofs more inclusive The key insight is that theimplementation of (le) doesnrsquot need to be the exact sameas leqGenericmdashit only needs to behave the same In otherwords all we need to be able to show that x le y is equiva-lent to leqGeneric x y for all x and y We factor out thistask into its own type class GOrd (short for ldquogeneric Ordrdquo)

class (Generic a Ord a Ord (Rep a)) rArr GOrd a where

genericLeqC Π (x y a)

rarr (x le y) sim (genericLeq x y)

defaultLeqTransitive

Π (x y z a)

rarr (VGeneric a VOrd (Rep a) GOrd a)

rArr So (x le y) rarr So (y le z) rarr So (x le z)

defaultLeqTransitive x y z xLeqY yLeqZ

| Refl larr genericLeqC x y

-- To conclude that

-- (So (x le y)) equals (So (genericLeq x y))

Refl larr genericLeqC y z

-- To conclude that

-- (So (y le z)) equals (So (genericLeq y z))

Refl larr genericLeqC x z

-- To conclude that

-- (So (x le z)) equals (So (genericLeq x z))

= leqTransitive (from x) (from y) (from z)

xLeqY yLeqZ

In order to use defaultLeqTransitive one must now pro-vide an appropriate instance of GOrd For datatypes like Twhere (le) = genericLeq defining a GOrd instance is a triv-ial task since genericLeqC can be implemented simply asλ_ _ rarr Refl For other datatypes more work is requiredalthough the amount of code one has to write to implementGOrd generally pales in comparison to the amount of codesaved by using the generic proofs in the first place As oneexample here is how one could use generic proofs for a Booldatatype whose Ord instance is defined in a nonndashdatatype-generic fashion

instance Ord Bool where

True le True = True

False le False = True

False le True = True

True le False = False

instance Generic Bool where

type Rep Bool =

from =

to =

instance GOrd Bool where

genericLeqC True True = Refl

genericLeqC False False = Refl

genericLeqC False True = Refl

genericLeqC True False = Refl

instance VOrd Bool where

leqTransitive = defaultLeqTransitive

There are a number of situations where this more permissiveform of generic defaults could be desirable We considerexamples of such situations in the remainder of this section

41 Proofs for Instances Defined ElsewhereIt is often the case that one wishes to prove properties ofdatatypes for which the implementation of their instancesis unchangeable For instance a datatype instance might bedefined in a library over which the proof author has no con-trol such as Haskellrsquos base library An example of this is theOrd instance for Bool which base defines in much the sameway as it is presented in Section 4 without using datatype-generic programming 5 It is unlikely that the maintainers of

5Strictly speaking this Ord Bool instance is defined using the derivingmechanism although the generated code would be similar in structure

22

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 9: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

base will change the implementation of this instance just tomake verifying it easier but thankfully this is not an issue inpractice Having permissive defaults for VOrd enables usersto write generic proofs for it without having to patch thesource code of base

The verified-classes library uses this technique in var-ious places to help write proofs for data types in base includ-ing proofs of the laws for the standard list typersquos Eq instanceand the laws for the Compose typersquos Applicative instancewhere Compose is defined as follows

newtype Compose f g a = MkCompose (f (g a))

In the particular case of Compose being able to use genericdefaults is a big win in terms of code size Writing an in-stance of GApplicative for Compose (which gives rise to aVApplicative instance that verifies the Applicative laws)only requires 109 lines of codeWriting the full VApplicativeproofs out by hand however would require 280 lines of code

42 Performance-Critical CodeThere are some situations in which compilers are unable tooptimize away the runtime overhead that datatype-genericprogramming can introduce [16 18] For these performance-sensitive situations this technique allows one to define classmethods in a performant way without datatype-generic pro-gramming while still benefitting from the proof automationthat it provides Even if the proofs themselves are defined us-ing datatype-generic programming as long as they are usedin a runtime-irrelevant fashion they do not risk introducingextra runtime costs

43 Working with StandardsSometimes the implementations of class methods are ex-pected to adhere to a common standard For instance con-sider the Traversable class from the Haskell base library

class Traversable t where

traverse Applicative f

rArr (a rarr f b) rarr t a rarr f (t b)

The documentation for Traversable6 requires that imple-mentations of traversemust satisfy traverse MkIdentity= MkIdentity where Identity must be defined as the fol-lowing datatype with this Applicative instance

newtype Identity a = MkIdentity a

instance Applicative Identity where

pure x = MkIdentity x

(MkIdentity f) ltgt (MkIdentity x) =

MkIdentity (f x)

While this Applicative Identity instance is convenientfor specifying the Traversable laws it enforces very partic-ular implementations of pure and (ltgt) that do not datatype-generic programming techniques Moreover Identity is a6httpshackagehaskellorgpackagebase-41200docsPreludehtmltTraversable

very commonly used datatype in the Haskell ecosystem inits own right so it is conceivable that one may wish to verifyits Applicative instance It would be a shame if we couldnot verify this instance generically simply because its docu-mentation required that it be implemented in a certain way

Fortunately we do not have to sacrifice convenience in thename of standardization Instead we simply define genericdefaults for VApplicative by leveraging a GApplicativeclass This way deriving proofs of the Applicative lawsfor Identity is tantamount to defining a GApplicativeIdentity instance which is not challenging Indeed this isthe approach that the verified-classes library adopts

5 EvaluationWe demonstrate the effectiveness of the techniques in the pa-per by implementing them in a library verified-classesThis library demonstrates the practical utility of these ideasby finding as many type classes with unchecked proof obliga-tions as possible from Haskellrsquos standard base package anddefining generic versions of each law It also offers insightinto the compile-time performance of writing proofs in thisstyle

51 The verified-classes LibraryFigure 3 categorizes every type class from base for whichverified-classes implements generic versions of its as-sociated laws 7 The classes in this figure are divided intotwo groups classes whose argument is of kind Type andclasses whose argument is of kind Type rarr Type The for-mer groups of classes are handled with the Genericmachin-ery introduced in Section 31 whereas the latter group ofclasses are handled by a slight variation of Generic calledGeneric1 Generic1 features mostly cosmetic changes fromGeneric to support datatypes of kind Type rarr Type

class Generic1 (f Type rarr Type) where

type Rep1 f Type rarr Type

from1 f a rarr Rep1 f a

to1 Rep1 f a rarr f a

Just as Generic has a verified counterpart in VGeneric sotoo does there exist a VGeneric1 class to ensure that aGeneric1 instancersquos implementations of from1 and to1 forma valid isomorphism

class Generic1 f rArr VGeneric1 f where

tf1 forall a Π (z f a)

rarr to1 (from1 z) sim z

ft1 forall a Π (r Rep1 f a)

rarr from1 (to1 r) sim r

7Note that there is no AbelianSemigroup class directly in base but sinceseveral Semigroup instances end up being commutative (or Abelian) inpractice we opted to include AbelianSemigroup as its own class for thesake of completeness

23

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 10: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

Class name Argument kind Laws Pattern functors SLoCAbelianSemigroup Type 1 V1 U1 K1 M1 () 50Alternative Type rarr Type 3 U1 M1 () Rec1 () 173Applicative Type rarr Type 5 U1 K1 M1 () Rec1 () 504Eq Type 3 V1 U1 K1 M1 () (+) 154Functor Type rarr Type 2 V1 U1 K1 M1 () (+) Par1 Rec1 () 167Monad Type rarr Type 3 U1 M1 () Par1 Rec1 350MonadPlus Type rarr Type 2 U1 M1 () Rec1 138MonadZip Type rarr Type 2 U1 M1 () Par1 Rec1 343Monoid Type 2 U1 K1 M1 () 94Ord Type 4 V1 U1 K1 M1 () (+) 232Semigroup Type 1 V1 U1 K1 M1 () 105Traversable Type rarr Type 3 V1 U1 K1 M1 () (+) Par1 Rec1 () 591

Figure 3 Every class from Haskellrsquos base library for which verified-classes offers generic defaults (The pattern functorsV1 M1 Par1 Rec1 and () are explained in Section 51)

To convey an approximate sense of how much effort thegeneric implementations of each classrsquos proofs requires Fig-ure 3 includes three metrics The numbers of laws that eachclass has as well as the source lines of code (SLoC) involvedin the generic implementation give a rough idea of howldquocomplexrdquo the proofs are for each class For instance veri-fiying Applicativersquos five laws takes 454 more SLoC thanAbelianSemigrouprsquos one law which supports the idea thatApplicative requires more work than AbelianSemigroupto verifyThe SLoC totals for some of these classes appear to be

surprisingly high and that is simply because the proofs forthese classes can become surprisingly long-winded Know-ing this can instill an appreciation for how many SLoC onesaves by using these generic defaults As one example theVTraversable instance for () alone takes about 110SLoC If one were to write a VTraversable instance by handfor a datatype with n + 1 fields then it would take approx-imately 110n SLoC just to write the parts of the proofs tohandle the fields alone

Also included in Figure 3 are the pattern functors that thegeneric implementations of each class implements Besidesbeing a rough metric for how complex a classrsquos proofs areseeing which pattern functors each class supports gives asense of how widely applicable each generic default is Forinstance it is not clear how one would generically writea Semigroup instance for (+) since one would have toarbitrarily choose between biasing towards L1 or R1 As aresult verified-classesrsquos generic Semigroup implemen-tation does not support sum types Other defaults are moreobvious For instance the generic implementations of EqFunctor Ord and Traversable mirror a similar strategythat GHC uses to generate code when these classes are placedin deriving clauses

Note that Figure 3 lists some pattern functors that werenot included in the simplified presentation in 1 Briefly theyare

bull V1 represents datatypes with no constructorsbull M1 only exists to bundle metadata such as constructornames and fixitiesbull Par1 represents occurrences of the last type parameter(Generic1 only)bull Rec1 represents occurrences of a type constructorapplied to the last type parameter (Generic1 only)bull () represents occurrences of the composition ofmultiple type constructors applied to the last type pa-rameter (Generic1 only)

52 Compilation Time ResultsWhile deriving proofs with datatype-generic programmingsaves authors fromwriting many lines of code one may won-der if there is a tradeoff between the size of the source codeand the time it takes to compile a program The proofs in thispaper can be deceptively small as they are actually doingquite a lot of work behind the scenes In this section weattempt to quantify the amount of work being done by mea-suring how long it takes to compile the verified-classeslibrary as well as some test programs that are built on topof verified-classes Our measurements were performedusing version 863 of the Glasgow Haskell Compiler (GHC)on a 4-core i5-4670 (34GHz 8GB) machine collecting thetimes reported by GHCrsquos -ddump-timings flag

521 Library Timing ResultsFigure 4 contains timing results for compiling eachmodule inverified-classes that defines a type class with proof obli-gations Each module was compiled with GHCrsquos three differ-ent optimization levels -O0 (no optimization) -O1 (moderateoptimization) and -O2 (max optimization) to give a sense of

24

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 11: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

Figure 4 The time it takes to compile each module in theverified-classes that defines a verified type class alongwith generic default implementations of each proof where-O0 through -O2 are the different levels of optimization

how different settings contribute to the overall compilationtimesNote that there are some module dependencies in this

figure For instance the Monoid class and its verified coun-terpart VMonoid have Semigroup and VSemigroup as su-perclasses respectively Therefore the figures for Monoidreflect only the laws that VMonoid adds on top of the existinglaws for VSemigroup not a cumulative total of all the lawsin both VSemigroup and VMonoidThe bulk of the classes in Figure 4 have fairly reason-

able compilation times most clocking in at under 5 secondsApplicative and Traversable both of which contain over500 SLoC apiece in their respective modules take notice-ably longer Even without optimization the Traversablemodule takes about 10 seconds to compile Applicativewhich takes about 4 seconds with -O0 balloons to about 19seconds with -O1 We will say more about this phenomenonin Section 523

522 Example Program Timing ResultsIn addition to measuring the time it took to compile theverified-classes library itself we alsomeasured the timesthat it took to compile representative sum and product typeswith several generically-verified instances The results inFigures 5 and 6 chart the times for SumEx the representa-tive sum type and ProductEx the representative producttype respectively Here are the definitions of SumEx andProductEx

data SumEx a

= MkSumEx1 | MkSumEx2 Unit

| MkSumEx3 a | MkSumEx4 (Pair Unit a)

data ProductEx a =

MkProductEx Unit a (Pair Unit a)

We say that SumEx and ProductEx are ldquorepresentativerdquo sincetheir generic representation types feature most of the pat-tern functors discussed earlier Since many of the classes

Figure 5 The time it takes to compile SumEx a representa-tive sum type as progressively more verified class instancesare defined using generic defaults -O0 through -O2 are thedifferent levels of optimization

Figure 6 The time it takes to compile ProductEx a repre-sentative product type as progressively more verified classinstances are defined using generic defaults -O0 through-O2 are the different levels of optimization Note that the x-axis in this figure uses a logarithmic scale whereas a linearscale is used in Figure 5

in verified-classes do not support sum types we useseparate sum and product types so that the product typecan demonstrate examples of instances that the sum typecould not have The Unit and Pair datatypes are convenientchoices for types of fields as they support instances of mostof the classes in verified-classesEach figure was measured by starting out with the bare-

bones definition of its corresponding datatype along withGeneric Generic1 VGeneric and VGeneric1 instancesthis data point is labeled as Base After this is measuredthe program is recompiled after adding generic Eq and VEqinstances this data point is labeled as Eq This process isrepeated for each label on the legend of each chart and thebars for each data point are stacked so as to reveal howmuchadditional time it took to compile every new additional setof classes

25

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 12: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

523 Timing Results and OptimizationFigures 4 5 and 6 all exhibit a curious spike in compilationtime when certain classes are compiled with optimizationThis is especially noticeable in the timing results for theTraversable and Applicative classes Surprisingly mostof the CPU time that GHC spends during compilation is notin the typechecking phase where one would typically expectlots of type-level computation to occur but instead duringthe simplifier phaseIn order to understand why GHC spends so much time

simplifying these programs it is helpful to know in a broadsense what happens when GHC compiles programs with de-pendent types GHC uses a well typed intermediate languagecalled Core that features explicit type-equality coercions [27]While coercions are erased before running a program theydo have an impact on compilation time as optimizationpasses over Core often have to manipulate coercions to typeof the Core involved In general the more coercions a pieceof Core has the longer it takes to optimize GHC even fea-tures a dedicated optimization pass that attempts to reducethe size of coercions so as to mitigate the effect that they canhave in later stages of compilation [33]

In picking a part of GHC to blame for the long compilationtimes observed in this evaluation the simplifierrsquos effect on co-ercions is the likeliest culprit Haskell programs with lots oftype-level computations typically correlate to Core with lotsof coercions especially in combination with the singletonsencoding As one extreme example compiling ProductExwith -O2 and all the verified classes from Figure 6 enabledinitially produces Core with about 143000 coercions directlyafter the typechecking phase is completed This is already asizable number as the overall Core has about 226000 nodestotal making over 60 of the AST consist of coercions Oncethe simplifier begins the number of coercions skyrocketseven further at one point reaching a maximum of over 45million

While the measurements in these figures may seem bleakwe have reason to be hopeful One silver lining is that GHC isleaving a lot of compilation performance on the table GHCtypically builds up coercions by combining many smallercoercions and given that the design of coercions mirrors in-ference rules of equational logic there is a lot of opportunityfor groups of coercions to become quite bloated One furtherpotential optimization is to ldquozaprdquo a group of coercions downinto a single placeholder coercion These placeholder coer-cions while less useful for debugging purposes are muchmore efficient to compile An experimental GHC patch8 thatzaps coercions after every step of normalization in type-level computation has shown to reduce the compile times ofa large program from 14811 seconds to 065 seconds We areoptimistic that coercion zapping could have similar benefitsfor our work

8httpsgitlabhaskellorgghcghcmerge_requests611

6 Generically Verifying Other LanguagesThe evaluation in Section 5 uses Haskell-plus-singletons(as described in Appendix A1 [21]) as the language to im-plement the ideas in this paper but they are by no meanslimited to this one language In this section we briefly con-sider implementations in two other languages Coq whichnatively supports dependent types as well as Liquid Haskella verification tool for Haskell

61 CoqCoq is an automated theorem prover based on the Calculus ofInductive Constructions Coq also supports type classes [25]which makes it a suitable target for datatype-generic proofsTo test this claim we have ported some of the functionalityfrom verified-classes to Coq For example here a genericproof of the fact that (le) is reflexive 9

Theorem defaultLeqReflexive

forall a Type `VGeneric a

`VOrd (Rep a) `Ord a

` GOrd a

(x a) So (leq x x)

Proof

intros rewrite genericLeqC

unfold genericLeq apply leqReflexive

Qed

Happily this approach meshes well with Coq As shown inthe Proof block one can even use Coqrsquos tactic system to fillin the implementation of the function if one desires

There is one conceptual difference betweenCoq andHaskellto be aware of when writing this style of proofs howeverUnlike Haskell which prevents defining multiple instancesfor the same type Coq has no such restriction By defaultwhen a type signature contains both Ord a and GOrd a con-straints Coq will not assume that the Ord a superclass ofGOrd a is the same as the Ord a listed in the type signatureThis motivates the use of the character before GOrd a inthe type signature above which instructs Coq to share theOrd a superclass with the one explicitly written out in thetype signature This is important since the proof will not gothrough if the two classes are not connected in this manner

62 Liquid HaskellLiquid Haskell [30] is a tool that verifies properties of Haskellcode using an SMT solver More recently Liquid Haskellhas implemented refinement reflection [32] which allowsspecifying arbitrary properties about functions and retrofitsHaskell into a theorem prover Refinement reflection closesthe gap between Liquid Haskell and languages that supportdependent types and one could envision a presentation ofthe techniques in this paper using Liquid Haskell instead ofsingletons9We rename (lt=) to leq to avoid clashing which Coqrsquos existing (lt=)notation

26

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 13: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

We originally explored implementing verified-classesusing Liquid Haskell but there exist technical issues whichprevent reflecting class methods in the same way that othertop-level functions can 10 We were able to work aroundthese issues by crudely replacing all type classes with reifieddictionary datatypes 11 although we felt this to be some-what unsatisfactory We hope that a future version of LiquidHaskell will make this style of programming more natural

7 Related Work71 Univalent Transport in Homotopy Type TheoryFundamentally our work generates proofs for datatypesby reusing proofs from the composition of several smallerdatatypes This technique bears a strong resemblance to theidea of transport in Homotopy Type Theory (HoTT) whichaims to carry over definitions and proofs from one datatypeto another datatype that is equivalent (ie there exists anisomorphism between the two types) Indeed recent workinto this area has demonstrated how to completely automatethis transport in Coq [28]

One potential drawback to using the techniques by Tabareauet al [28] is that some proofs must rely on the univalenceaxiom which asserts that syntactic equivalence is equivalentto equality Because univalence is not computable any proofthat uses univalence will itself be uncomputable Tabareauet al [28] identify a subset of the Calculus of Inductive Con-structions in which transport is guaranteed to be free fromuses of this axiom but imposes some restrictions on thetypes involved (eg all indexed inductive families involvedmust have decidable equality)In contrast our approach does not rely on univalence

which means that the vast majority of our generated proofsare computable Some of the particular proofs in verified-classes do rely on the function extensionality axiom (egthe proofs of the Functor laws which involve statementsabout higher-order functions) but this is a property that weshare in common with Tabareau et al [28] which also usesfunction extensionality to prove a theorem about transport-ing dependent products

72 Other Datatype-Generic Programming StylesThe techniques presented in this paper rely on the patternfunctor style of generic programming This is only one wayto write datatype-generic programs and other work ap-proaches datatype-generic programming in a dependentlytyped setting differently

10See httpsgithubcomucsd-progsysliquidhaskellissues119611See httpsgithubcomiu-parfuncverified-instancestree1110df2516bd059336fc37c018d7292a340918a8

Benke et al [6] Altenkirch et al [4] and Chapman et al[9] demonstrate how to achieve datatype-generic program-ming by first writing codes in a universe of inductively de-fined sets and then generically programming over the in-terpretations of those codes Benke et al [6] in particulardemonstrates that this technique can be used to derive proofsof reflexivity and substitutivity in a generic equality test Al-Sibahi [2] implements a generic programming library in Idrislargely based on these ideas and uses it to derive instances ofEq Ord Functor Applicative and Traversable as wellas proofs of their algebraic properties [3]The ldquouniverserdquo style of dependently typed generic pro-

gramming as presented in Al-Sibahi [2] is perhaps the mostclosely related existing relative to the ideas in this paper asboth styles involve constructing descriptions of datatypes(similar to representation types) converting to and from thedescriptions and generically writing proofs over a minimaluniverse datatype (similar to pattern functors) Howeverthese proofs assume particular generic implementations oftype class methods whereas our system is more flexible inwhat it allows thanks to the techniques in Section 4

8 Future Directions81 Verifying Other Derived InstancesHaskell features a deriving mechanism that supports au-tomatic generation of instances for commonly used typeclasses such as Eq Ord and Functor While it is gener-ally believed that deriving generates code that is lawfulthis is not a claim that has been formally verified Indeedthe volume of open GHC tickets containing the keywordldquoderivingrdquo mdash36 at the time of writing 12mdash suggests that ver-ification could help increase user confidence that derivedinstances are lawfulThe work in this paper addresses this concern to a lim-

ited extent as the VGeneric class can verify that derivedGeneric instances behave correctly It would be interestingto extend this treatment to every other derivable type classin Haskell This would not be entirely straightforward to dohowever as some derived instances use low-level tricks forperformance reasons For example derived Ord instancessometimes use the primitive dataToTag function to moreefficiently compare enumeration types as machine integers13 We are unaware of a way to use dataToTag at the typelevel so it remains to be seen how low-level derived codecould be verified as-is

82 More Sophisticated Generic Programming StylesOur work leverages the pattern functor style of datatype-generic programming while other work uses inductively

12According to httpsgitlabhaskellorgghcghcissuesscope=allamputf8=E29C93ampstate=openedamplabel_name[]=deriving13See also httpsgitlabhaskellorgghcghcissues15696 for an example ofa bug caused by a bad interaction between deriving Ord and dataToTag

27

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 14: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Haskell rsquo19 August 22ndash23 2019 Berlin Germany Ryan G Scott and Ryan R Newton

defined universes generic combinators and univalence asdiscussed in Section 72 It would be interesting to extendthese ideas to other forms of datatype-generic programmingsuch as the sums of products approach that has seen popular-ity in recent years [10]Our work uses representation types from GHCrsquos own

GHCGenerics module which is essentially a faithful repro-duction of the work in Magalhatildees et al [17] While theserepresentation types are nice to work with because of theirrelative simplicity they are somewhat limited in their abilityto represent advanced language features such as polymor-phic kinds or GADTs We anticipate that the same tech-niques from this paper could also be applied to more so-phisticated datatype-generic programming libraries that dosupport these advanced features such as the libraries pre-sented in Serrano and Miraldo [22 23]

83 External Verification of CodeWe have explored a solution for defining and verifying codewithin the same language and in the particular case ofverified-classes the language is Haskell Some previ-ous efforts to verify Haskell code take a different approachand verify Haskell code using an external tool such as SMTsolvers [30] Coq [8 26] Alfa [15] or Agda [1] An externalverification tool could serve as the basis for deriving proofsas the implementations of the generic class methods couldlive in Haskell while the generic proofs could be constructedin the external tool separately This could also be used toldquobootstraprdquo languages that lack dependent types

9 ConclusionWehave demonstrated how datatype-generic programming agravela pattern functors significantly streamline many class-basedproofs that would otherwise require excessive amounts ofboilerplate to implement The idea of reducing the imple-mentation down to minimal pattern functor types greatlyreduces the surface area that a library author has to coveras verifying the laws for other datatypes becomes a muchmore manageable task of picking a suitable generic defaultOur techniques are general as they can be ported to anydependently typed programming language with the facilitiesto reason about type classes Moreover our design is flexibleenough to adapt to existing class instances that were notthemselves defined using generic programming allowingthis design to coexist with other code ldquoin the wildrdquo The im-plementation of these ideas in verified-classes offers ablueprint for this can work for several notable classes withproof obligations

AcknowledgmentsWe thank the anonymous reviewers for their feedback Spe-cial thanks goes to Vikraman Choudhury Niki Vazou andRanjit Jhala for their contributions on a previous version of

this paper This material is based upon work supported bythe National Science Foundation under Grant No 1453508

References[1] Andreas Abel Marcin Benke Ana Bove John Hughes and Ulf Norell

2005 Verifying Haskell Programs Using Constructive Type Theory InProceedings of the 2005 ACM SIGPLAN Workshop on Haskell (Haskellrsquo05) ACM New York NY USA 62ndash73 httpsdoiorg10114510883481088355

[2] Ahmad Salim Al-Sibahi 2014 The Practical Guide to Levitation Mas-terrsquos thesis IT University of Copenhagen Copenhagen Denmarkhttpitudkpeopleasalpubsmsc-thesis-reportpdf

[3] Ahmad Salim Al-Sibahi 2019 Descrsquon crunchhttpsgithubcomahmadsalimdesc-n-crunchtree6da2675bb4e2f5386c9f6a264ffd64846d11baa9 Accessed 2019-01-23

[4] Thorsten Altenkirch Conor McBride and Peter Morris 2007 GenericProgramming with Dependent Types In Proceedings of the 2006 In-ternational Conference on Datatype-generic Programming (SSDGPrsquo06)Springer-Verlag Berlin Heidelberg 209ndash257 httpdlacmorgcitationcfmid=17828941782898

[5] Jeremy Avigad Leonardo de Moura and Soonho Kong 2017 TypeClasses httpsleanprovergithubiotheorem_proving_in_leantype_classeshtml Accessed 2019-01-23

[6] Marcin Benke Peter Dybjer and Patrik Jansson 2003 Universes forGeneric Programs and Proofs in Dependent Type Theory Nordic J ofComputing 10 4 (Dec 2003) 265ndash289 httpdlacmorgcitationcfmid=985799985801

[7] Edwin Brady 2013 Idris a general-purpose dependently typed pro-gramming language Design and implementation Journal of FunctionalProgramming 23 5 (2013) 552ndash593

[8] Joachim Breitner Antal Spector-Zabusky Yao Li Christine RizkallahJohn Wiegley and Stephanie Weirich 2018 Ready Set Verify Ap-plying Hs-to-coq to Real-world Haskell Code (Experience Report)Proc ACM Program Lang 2 ICFP Article 89 (July 2018) 16 pageshttpsdoiorg1011453236784

[9] James Chapman Pierre-Eacutevariste Dagand Conor McBride and PeterMorris 2010 The Gentle Art of Levitation In Proceedings of the 15thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo10) ACM New York NY USA 3ndash14 httpsdoiorg10114518635431863547

[10] Edsko de Vries and Andres Loumlh 2014 True Sums of Products InProceedings of the 10th ACM SIGPLAN Workshop on Generic Pro-gramming (WGP rsquo14) ACM New York NY USA 83ndash94 httpsdoiorg10114526336282633634

[11] Dominique Devriese and Frank Piessens 2011 On the Bright Side ofType Classes Instance Arguments in Agda In Proceedings of the 16thACM SIGPLAN International Conference on Functional Programming(ICFP rsquo11) ACM New York NY USA 143ndash155 httpsdoiorg10114520347732034796

[12] Richard A Eisenberg 2016 Dependent Types in Haskell Theory andPractice arXivcsPL161007978

[13] Richard A Eisenberg and StephanieWeirich 2012 Dependently TypedProgramming with Singletons In Proceedings of the 2012 Haskell Sym-posium (Haskell rsquo12) ACM New York NY USA 117ndash130 httpsdoiorg10114523645062364522

[14] Florian Haftmann and Makarius Wenzel 2007 Constructive TypeClasses in Isabelle In Proceedings of the 2006 International Conference onTypes for Proofs and Programs (TYPESrsquo06) Springer-Verlag Berlin Hei-delberg 160ndash174 httpdlacmorgcitationcfmid=17892771789288

[15] Thomas Hallgren James Hook Mark P Jones and Richard B Kieburtz2004 An overview of the programatica toolset In High ConfidenceSoftware and Systems Conference HCSS04 httpwww cse ogi edu˜hallgrenProgramaticaHCSS04 Citeseer

28

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References
Page 15: Generic and Flexible Defaults for Verified, Law-Abiding ... · adding full-spectrum dependent types [12, 35], and deter-mined Haskell users can already write many dependently typed

Generic and Flexible Defaults for Verified Law-Abiding Type-Class Instances Haskell rsquo19 August 22ndash23 2019 Berlin Germany

[16] Joseacute Pedro Magalhatildees 2013 Optimisation of Generic ProgramsThrough Inlining 104ndash121 httpsdoiorg101007978-3-642-41582-1_7

[17] Joseacute Pedro Magalhatildees Atze Dijkstra Johan Jeuring and Andres Loumlh2010 A Generic Deriving Mechanism for Haskell In Proceedings ofthe Third ACM Haskell Symposium on Haskell (Haskell rsquo10) ACM NewYork NY USA 37ndash48 httpsdoiorg10114518635231863529

[18] Joseacute Pedro Magalhatildees Stefan Holdermans Johan Jeuring and AndresLoumlh 2010 Optimizing Generics is Easy In Proceedings of the 2010 ACMSIGPLAN Workshop on Partial Evaluation and Program Manipulation(PEPM rsquo10) ACM New York NY USA 33ndash42 httpsdoiorg10114517063561706366

[19] Guido Martiacutenez 2018 Typeclasses (via meta arguments)httpsgithubcomFStarLangFStarwikiTypeclasses-(via-meta-arguments)c5719ae49bc8a00fa231057a94a8edee1db2a704 Accessed2019-01-23

[20] Thomas van Noort Alexey Rodriguez Stefan Holdermans Johan Jeur-ing and Bastiaan Heeren 2008 A Lightweight Approach to Datatype-generic Rewriting In Proceedings of the ACM SIGPLAN Workshop onGeneric Programming (WGP rsquo08) ACM New York NY USA 13ndash24httpsdoiorg10114514113181411321

[21] Ryan G Scott and Ryan R Newton 2019 Generic and Flexible Defaultsfor Verified Law-Abiding Type-Class Instances (Appendix)

[22] Alejandro Serrano and Victor Cacciari Miraldo 2018 Generic Pro-gramming of All Kinds In Proceedings of the 11th ACM SIGPLANInternational Symposium on Haskell (Haskell 2018) ACM New YorkNY USA 41ndash54 httpsdoiorg10114532427443242745

[23] Alejandro Serrano and Victor Cacciari Miraldo 2019 Classes of Arbi-trary Kind In International Symposium on Practical Aspects of Declara-tive Languages Springer 150ndash168

[24] Tim Sheard and Simon Peyton Jones 2002 Template Meta-programming for Haskell SIGPLAN Not 37 12 (Dec 2002) 60ndash75httpsdoiorg101145636517636528

[25] Matthieu Sozeau and Nicolas Oury 2008 First-Class Type Classes InProceedings of the 21st International Conference on Theorem Proving inHigher Order Logics (TPHOLs rsquo08) Springer-Verlag Berlin Heidelberg278ndash293 httpsdoiorg101007978-3-540-71067-7_23

[26] Antal Spector-Zabusky Joachim Breitner Christine Rizkallah andStephanie Weirich 2018 Total Haskell is Reasonable Coq In Proceed-ings of the 7th ACM SIGPLAN International Conference on CertifiedPrograms and Proofs (CPP 2018) ACM New York NY USA 14ndash27

httpsdoiorg1011453167092[27] Martin Sulzmann Manuel M T Chakravarty Simon Peyton Jones

and Kevin Donnelly 2007 System F with Type Equality CoercionsIn Proceedings of the 2007 ACM SIGPLAN International Workshop onTypes in Languages Design and Implementation (TLDI rsquo07) ACM NewYork NY USA 53ndash66 httpsdoiorg10114511903151190324

[28] Nicolas Tabareau Eacuteric Tanter and Matthieu Sozeau 2018 Equiv-alences for Free Univalent Parametricity for Effective TransportProc ACM Program Lang 2 ICFP Article 92 (July 2018) 29 pageshttpsdoiorg1011453236787

[29] Ron Van Kesteren Marko Van Eekelen and Maarten De Mol 2006Proof support for general type classes Trends in Functional Program-ming 5 (2006) 1ndash16

[30] Niki Vazou Eric L Seidel Ranjit Jhala Dimitrios Vytiniotis and SimonPeyton-Jones 2014 Refinement Types for Haskell SIGPLAN Not 499 (Aug 2014) 269ndash282 httpsdoiorg10114526929152628161

[31] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan GScott Ryan R Newton Philip Wadler and Ranjit Jhala 2017 Ex-tended Version Refinement Reflection Complete Verification withSMT httpsnikivazougithubiostaticpopl18extended-refinement-reflectionpdf

[32] Niki Vazou Anish Tondwalkar Vikraman Choudhury Ryan G ScottRyan R Newton Philip Wadler and Ranjit Jhala 2017 RefinementReflection Complete Verification with SMT Proc ACM Program Lang2 POPL Article 53 (Dec 2017) 31 pages httpsdoiorg1011453158141

[33] Dimitrios Vytiniotis and Simon Peyton Jones 2013 Evidence normal-ization in system FC Leibniz International Proceedings in InformaticsLIPIcs 21 (01 2013) 20ndash38 httpsdoiorg104230LIPIcsRTA201320

[34] P Wadler and S Blott 1989 How to Make Ad-hoc Polymorphism LessAd Hoc In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (POPL rsquo89) ACM New YorkNY USA 60ndash76 httpsdoiorg1011457527775283

[35] Stephanie Weirich Antoine Voizard Pedro Henrique Azevedo deAmorim and Richard A Eisenberg 2017 A Specification for De-pendent Types in Haskell Proc ACM Program Lang 1 ICFP Article31 (Aug 2017) 29 pages httpsdoiorg1011453110275

[36] Alexey Rodriguez Yakushev Stefan Holdermans Andres Loumlh andJohan Jeuring 2009 Generic Programming with Fixed Points forMutually Recursive Datatypes In Proceedings of the 14th ACMSIGPLANInternational Conference on Functional Programming (ICFP rsquo09) ACMNew York NY USA 233ndash244 httpsdoiorg10114515965501596585

29

  • Abstract
  • 1 Introduction
  • 2 Background
    • 21 A Tour of Verified Classes
      • 3 Verified Instances Generically
        • 31 A Primer on Datatype-Generic Programming
        • 32 Verifying Generic
        • 33 Orderings on Representation Types
        • 34 Proofs over Representation Types
        • 35 Carrying Over Proofs
          • 4 More Permissive Generic Defaults
            • 41 Proofs for Instances Defined Elsewhere
            • 42 Performance-Critical Code
            • 43 Working with Standards
              • 5 Evaluation
                • 51 The verified-classes Library
                • 52 Compilation Time Results
                  • 6 Generically Verifying Other Languages
                    • 61 Coq
                    • 62 Liquid Haskell
                      • 7 Related Work
                        • 71 Univalent Transport in Homotopy Type Theory
                        • 72 Other Datatype-Generic Programming Styles
                          • 8 Future Directions
                            • 81 Verifying Other Derived Instances
                            • 82 More Sophisticated Generic Programming Styles
                            • 83 External Verification of Code
                              • 9 Conclusion
                              • Acknowledgments
                              • References