equality proofs

Upload: blulaktuko

Post on 27-Feb-2018

215 views

Category:

Documents


0 download

TRANSCRIPT

  • 7/25/2019 Equality proofs

    1/12

    Equality Proofs and Deferred Type ErrorsA Compiler Pearl

    Dimitrios Vytiniotis Simon Peyton Jones

    Microsoft Research, Cambridge{dimitris,simonpj}@microsoft.com

    Jose Pedro Magalhaes

    Utrecht [email protected]

    Abstract

    The Glasgow Haskell Compiler is an optimizing compiler that ex-presses and manipulates first-class equality proofs in its intermedi-ate language. We describe a simple, elegant technique that exploitsthese equality proofs to supportdeferred type errors. The techniquerequires us to treat equality proofs as possibly-divergent terms; weshow how to do so without losing either soundness or the zero-overhead cost model that the programmer expects.

    Categories and Subject Descriptors D.3.3 [Language Constructsand Features]: Abstract data types; F.3.3 [Studies of ProgramConstructs]: Type structure

    General Terms Design, Languages

    Keywords Type equalities, Deferred type errors, System FC

    1. Introduction

    In a compiler, a typed intermediate language provides a firm placeto stand, free from the design trade-offs of a complex source lan-guage. Moreover, type-checking the intermediate language pro-vides a simple and powerful consistency check on the earlier stagesof type inference and other optimizing program transformations.The Glasgow Haskell Compiler (GHC) has just such an interme-diate language. This intermediate language has evolved in the lastfew years from System F to System FC (Sulzmann et al. 2007;Weirich et al. 2011) to accommodate the source-language featuresof GADTs (Cheney and Hinze 2003; Peyton Jones et al. 2006;Sheard and Pasalic 2004) and type families (Chakravarty et al.

    2005;Kiselyov et al. 2010); and from System FC to System FC, a

    calculus now fully equipped with kind polymorphismand datatypepromotion(Yorgey et al. 2012).

    The principal difference between System F and System FC is that,

    together with type information, System FCcarriesequality proofs:

    evidence that type equality constraints are satisfied. Such proofsare generated during the type inference process and are useful for

    type checking System FC programs. However, once type checking

    of the FC program is done, proofs very much like types can be

    Permission to make digital or hard copies of all or part of this work for personal orclassroom use is granted without fee provided that copies are not made or distributedfor profit or commercial advantage and that copies bear this notice and the full citationon the first page. To copy otherwise, to republish, to post on servers or to redistributeto lists, requires prior specific permission and/or a fee.

    ICFP12, September 915, 2012, Copenhagen, Denmark.Copyright 2012 ACM 978-1-4503-1054-3/12/09. . . $10.00

    completely (and statically) erased, so that they induce no runtimeexecution or allocation overhead.

    Proof assistants and dependently typed languages (Bove et al.2009;Norell 2007; The Coq Team)adopt a similar design with staticallyerasable proofs, including ones that go beyond equality to morecomplex program properties. However, there is one important dif-ference: in proof assistants the proof language is the computationlanguage, always a side-effect free and terminating language that

    guarantees logical consistency of the proofs. On the other hand,in System F

    C the computation language includes partial functions

    and divergent terms. To ensure logical consistency, FC keeps the

    equality proof language as a syntactically separate, consistent-by-construction set of equality proof combinators.

    In this paper we investigate the opportunities and challenges ofblurring the rigid proof/computation boundary, without threateningsoundness, by allowing proof-like first-class values to be returnedfrom ordinary (even divergent or partial) computation terms. Wemake the following contributions:

    The proofs-as-values approach opens up an entirely new pos-sibility, that of deferring type errors to runtime. A commonobjection to static type systems is that the programmer wants

    to be able to run a program even though it may contain sometype errors; after all, the execution might not encounter the er-ror. Recent related work (Bayne et al. 2011)makes a convinc-ing case that during prototyping or software evolution program-mers wish to focus on getting partof their code right, withoutfirst having to get all of it type-correct. Deferring type errorsseems to be just the right mechanism to achieve this. Our newapproach gives a principled way in which such erroneous pro-grams can be run with complete type safety (Sections 3and5).

    The key to the almost effortless shift to proofs-as-values is

    based on a simple observation: System FC, with the recent addi-

    tion of kind polymorphism (Yorgey et al. 2012), already allowsus to define within the system an ordinary first-class type fortype equality (Section 4). As such, we can have ordinary val-ues of that type, that are passed to or returned from arbitrary

    (even partial or divergent) terms. Moreover, deferring type er-rors aside, there are other compelling advantages of proofs-as-values in an evidence-passing compiler, as we outline in Sec-tion 6.

    Most functional programmers think of types as static objects,with zero run-time overhead, and they expectthe same of proofsabout types. Treating type equality proofs as values seriouslyundermines this expectation. InSection 7we address this chal-lenge and show how the optimizer of GHC, with no changeswhatsoever, can already eliminate the cost of equality proofs except in corner cases where it would be wrong to do so.

  • 7/25/2019 Equality proofs

    2/12

    Everything we describe is fully implemented. As far as we know,GHC is the first, and only, widely-used optimizing compiler thatmanipulates first-class proofs. We describe this paper as a pearlbecause it shows a simple and elegant way in which the apparently-esoteric notion of a proof object can be deployed to solve a verypractical problem.

    2. The opportunity: deferring type errors

    Suppose you type this Haskell term into the interactive read-eval-print prompt in GHCi:

    ghci> fst (True, a && False)

    This term does not go wrong when evaluated: you might expectto just get back the result Truefrom projecting the first componentof the pair. But in a statically typed language like Haskell you getthe type error:

    Couldnt match Bool with CharIn the first argument of (&&), namely a

    This behaviour is fine for programs that are (allegedly) finished, butsome programmers would much prefer the term to evaluate to True

    when doing exploratory programming. After all, if the error is ina bit of the program that is not executed, it is doing no harm! Inparticular, when refactoring a large program it is often useful to beable to run parts of the completed program, but type errors preventthat. What we want is to defer type errors until they matter. We havemore to say about motivation and related work in Section 8.

    As we shall see, System FC allows us to offer precisely this be-

    haviour,without giving up type safety. Here is an interactive sessionwithghci -fdefer-type-errors:

    ghci> let foo = (True, a && False)Warning: Couldnt match Bool with Charghci> :type foo(Bool, Bool)ghci> fst foo

    Trueghci> snd fooRuntime error: Couldnt match Bool with Char

    Notice that:

    The definition offooproduced a warning (rather than an error),but succeeds in producing an executable binding for foo.

    Since type checking offoo succeeded it has a type, which canbe queried with:typeto display its type, (Bool,Bool).

    The term fst foo typechecks fine, and also runs fine, returningTrue.

    The term snd foo also typechecks fine, and runs; however theevaluation aborts with a runtime error giving exactly the sameerroras the original warning.

    That is, the error message is produced lazily, at runtime, when andonly when the requirement for Charand Boolto be the same typeis encountered.

    2.1 How deferring type errors works, informally

    GHCs type inference algorithm works in two stages: first we gen-erate type constraints, and then we solve them (Vytiniotis et al.2011). In addition, inference elaborates the Haskell source term to

    an explicitly typed FCterm, that includes the types and proofs (ev-

    idence in GHC jargon) computed by the constraint solver.

    In the previous example, during type inference for the sub-terma && False we generate a type equality constraint, writtenChar Bool. Usually the constraint solver would immediately re-

    ject such a constraint as insoluble, but with -fdefer-type-errorswe take a different course: we generate evidence for Char

    Bool, but ensure that if the (bogus) evidence is ever evaluated itbrings the program to a graceful halt. More concretely, here is the

    F

    C term that we generate for foo:foo=let(c:CharBool) =error"Couldnt..."

    in(True,(castac)&&False)

    The elaboratedfoocontains a lazy binding of an evidence variablec of type Char Bool to a call to error. The latter is a built-inHaskell constant, of type a. String a, that prints its argumentstring and brings execution to a halt.

    When we evaluate fst foo the result is True; but if we evaluatesnd foo, we must evaluate the result of(&&), which in turn evalu-ates its first argument,casta c. The cast forces evaluation ofc,and hence triggers the runtime error. Note that the exact placementof coercions, and thus which errors get deferred, depends on the in-ternals of the type inference process; we discuss this in more detailinSection 5.4.

    There is something puzzling about binding variable cwith the typeChar Bool. The evidence variable c is supposed to be boundto a proof witnessing that Char and Bool are equal types, but isnevertheless bound to just a term, and in fact a crashing term,namely error! How can we then ensure soundness, and how can weget statically erasable proofs? It turns out that the typeCharBoolis almost but not quite the type of a proof object. To explain howthis works, we move on to present some more details on GHCs

    typed intermediate language, System FC.

    3. The FC language

    System FC is a polymorphic and explicitly typed language, whose

    syntax is given in Figure 1. Our presentation closely follows the

    most recent work on FCYorgey et al. (2012), and we will not repeatoperational semantics and type soundness results; instead, we referthe reader toYorgey et al. (2012) for the details.

    A quick glance atFigure 1 will confirm that the term language eis mostly conventional, explicitly-typed, lambda calculus, with let-bindings, literals (l), data constructors (K), and case expressions.In addition, the language includes type and kindpolymorphism:type (a:.e) and kind (.e) abstractions, and type (e) andkind (e ) applications, respectively. Some motivation for kindabstractions and applications comes from previous work but, as weshall see in Section 6.2, kind polymorphism will play a key rolehere as well.

    The distinctive feature of FCis the use ofcoercions, . A coercion

    of type #is nothing more than aproof of type equality betweenthe typesand . Contrary to the notation used inSection 2.1and

    in previous presentations of System FC notice that we use symbol# instead of for coercion types, and Constraint# rather thanConstraintfor their kind this is for a good reason that will becomeevident inSection 4.2.

    The term(e )is a castthat converts a term eof typeto one oftype, when: #. Once again, this is deliberately differentthan the castterm that appeared in Section 2.1, as we discuss inSection 4.2. The only other place where a coercion may appearin the term language is in an application (e), so coercions are notfirst-class values. Dually, one can abstract over such coercions witha coercion abstraction(c:#).e.

  • 7/25/2019 Equality proofs

    3/12

    Termse,u ::= x|l|x:.e|e u

    | a:.e|e| .e|e Type/kind polymorphism| c:.e|e Coercion abs/app| K| caseeofpu| letx:= einu| e Cast

    p ::= K c:x: Patterns

    Types,,, ::= a Variables

    | H Constants| F Type functions| 12 Application| Kind application| a:. Polymorphic types| . Kind-polymorphic types

    Type constantsH ::= T Datatypes

    | () Arrow| (#) Primitive equality type

    Kinds, ::= | |

    | . Polymorphic kinds

    | Constraint# Kind of static proofs

    Coercion values, ::= c Variables

    | 12 Application| Reflexivity| 1; 2 Transitivity| sym Symmetry| . . . Other coercion forms

    Environments, ::= | ,bndbnd ::= Kind variable

    | a: Type variable| c:# Coercion variable| x: Term variable| T: . Data type| K:(a:

    a). Ta Data constructor

    | . . . Type families etc.NotationT T1 . . .m1 . . .n 1. . . n

    foreitheror

    0 initial (closed) environment (#):. Constraint#

    Figure 1:Syntax of System FC (excerpt)

    The syntax of coercions themselves ( inFigure 1) includes coer-cion variables, constructors for reflexivity, transitivity, and symme-try, as well as other constructors (such as lifting type equalities overdata constructors) that we do not need to discuss in this paper.

    The well-formedness judgement for terms appears in Figure 2and is mostly conventional. In particular, the rules for coercionabstraction and application (ECABS and ECAPP) mirror those forterms (EABS and EAPP). The rule for case expressions (ECAS E) isalso standard but notice that it allows us to bind coercion variables,as well as term variables, in a pattern.

    3.1 Types, kinds, and kind polymorphism

    The type language of System FC includes variables and constants,

    type and kind application, as well as the type of type-polymorphic

    tm e:

    (x:) EVAR

    tm x:

    (K:) 0EC ON

    tm K:

    ,(x:)tm e:ty :

    EA BStm x:.e:

    tm e: tm u:

    EA PPtm e u:

    ,(c:)tm e:ty : Constraint# ECABStm c:.e:

    tm e:(1#2)co : 1#2 ECAPP

    tm e:

    k ,(a:)tm e:

    ETABStm a:.e:a:.

    tm e:a:.ty :

    ETAPPtm e: [/a]

    ,tm e:EK ABS

    tm .e:.

    tm e:.k

    EKA PPtm e: [/]

    ,(x:)tm u:,(x:)tm e:

    EL ETtm letx:= uine:

    tm e:co : # EC AS Ttm e :

    tm e:T For each branchK x:u

    (K:(a:a).1#2 Ta) 0i=i[/][/a]1i=1i[/][/a]2i=2i[/][/a] ,c:12x:

    tm u:EC ASE

    tm caseeofK(c:1#2) (x:)u:

    Figure 2:Well-formed terms

    values (a:.) and the type of kind-polymorphic values (.).The type constants Hinclude data constructors (T), and the func-tion constructor () as well as the equality constructor (#). Thewell-formedness judgement for types appears inFigure 3.

    What should the kind of# be? We mentioned previously that wewould like to classify any type #as having kindConstraint#,but the kind ofand can beanykind whatsoever. This indicatesthat#should be given thepolymorphickind:

    . Constraint#

    This kind, made valid because the syntax of kindsincludes kindpolymorphism, is recorded in the initial environment 0(bottom ofFigure 1). Well-formedness of kinds (k ), for this presentation,amounts to well-scoping, so we omit the details from Figure 3.Asa convention, we write # to mean (#) in the rest ofthis paper, where the kindofand is clear from the context.

    Finally, notice that well-formed arrow types1 are allowed to acceptan argument which is either Constraint# or , to account for coer-cion or term abstraction, but may only return , hence disallowingany functions to return a value of type #. However, as we will

    1 For simplicity of presentation we do not include a binding for () inthe initial environment 0, hence only allowing fully applied arrow types,unlike Haskell.

  • 7/25/2019 Equality proofs

    4/12

    ty :

    (a: ) TVAR

    ty a:

    (T: ) TDATA

    ty T:

    ty 1: 1 ty 2:

    1 {,Constraint#} TARRty 12:

    ty 1: 12ty 2: 1

    TAPPty 12: 2

    ty : .k

    TKA PPty : [/]

    ,(a:)ty : k

    TALLty a:.:

    ,ty :

    TKA LLty .:

    co : 1#2

    . . .

    k

    . . .

    Figure 3:Well-formed types and coercions

    see inSection 4,a function can well return terms that containsuchcoercions.

    3.2 FC datatypes with coercions

    In FC, coercions can appear as arguments to data constructors,

    a feature that is particularly useful for representing generalizedalgebraic datatypes (GADTs) (Peyton Jones et al. 2006). ConsiderthissourceHaskell program which defines and uses a GADT:

    dataT awhereT1 :: IntT IntT2 :: a T a

    f:: T a[ a]f(T1x) = [x + 1]f(T2v) = [ ]

    main=f(T14)

    In FC, we regard the GADT data constructor T1as having the type:

    T1:a.(a#Int)IntT a

    So T1 takes three arguments: a type argument to instantiate a, acoercion witnessing the equality between a and Int, and a value of

    typeInt. Here is the FCversion ofmain:

    main=f Int(T1IntInt4)

    The coercion argument has kind Int#Int, for which the evidenceis just Int (reflexivity). Similarly, pattern-matching on T1 bindstwo variables: a coercion variable, and a term variable. Here is the

    FCelaboration of function f:

    f= (a:) .(x:T a) .casexof

    T1(c:a#Int) (n:Int)(Cons(n + 1)Nil)sym[c]

    T2vNil

    The cast converts the type of the result from [Int] to [a]2. Thecoercionsym [c]is evidence for (or a proof of) the equality of thesetypes, lifting c (of type a #Int) over lists ([c], oftype [a] #[Int]),before applying symmetry. We urge the reader to consultSulzmannet al. (2007) and Weirich et al. (2011) for more examples andintuition.

    A final remark: we will be presenting and discussing a number of

    FC programs in the rest of the paper. For readability purposes wewill sometimes omit type or kind applications in F

    C terms when

    these types or kinds are obvious from the context, making thesyntax appear less verbose.

    4. Two forms of equality

    InSection 2we sketched how to use type-equality evidence to sup-port deferred type errors, using as the type of equality evi-dence. Then inSection 3we introduced our intermediate language,

    System FC, in which explicit coercions of type #represent ev-

    idence for the equality of two types. The distinction between ()and(#)is crucial: it allows us to to marry a sound, erasable lan-guage of proofs with the potentially-unsound ability to have terms

    that compute proofs, as we discuss in this section.

    4.1 The tension

    Types have a very valuable property that programmers take forgranted: they give strong static guarantees, but they carry no run-time overhead. This zero-overhead guarantee is formally justifiedby an erasureproperty: we can erase all the types before runningthe program, without changing the result.

    Can we offer a similar guarantee for coercions? Yes, we can. Sys-

    tem FCis carefully designed so that coercion abstractions, coercion

    applications, and casts, are all statically erasable, just like type ab-stractions and applications.

    But this statement is manifestly in tension with our approach to

    deferred type errors. Consider once more the FC term

    let(c:CharBool) =error"Couldnt match..."insnd(True,(castac)&&False)

    Obviously we cannot erase the binding ofc and the cast, leavingsnd(True,a && False), because the latter will crash. So it seemsthat insisting on complete erasure of equalities kills the idea ofdeferring type errors stone dead!

    4.2 The two equalities

    We now present our resolution of this tension. We have carefullymaintained a distinction between

    (#), the type of primitive coercions in FC, which are fully

    erasable, and

    (), type of evidence generated by the type inference engine,which cannot be erased.

    However() is not some magical built-in device. Rather, we candefine it as a perfectly ordinary GADT (like the one we havealready seen in Section3.2), thus:

    dataabwhereEq# :: (a#b)ab

    2 We informally use Haskells notation[ ] for the type list of, and ConsandNil as its constructors.

  • 7/25/2019 Equality proofs

    5/12

    This definition introduces a new algebraic data type constructor(), belonging in the T syntactic category of Figure 1. It hasexactly one data constructor Eq#, whose (important) argument isa static equality proof. Readers familiar with proof assistants ortype theory will immediately recognize in the definition of()thetype used traditionally in such systems to internalize definitionalequalityas a type (e.g.refl_equalin Coq).

    Like(#), the data type()is polymorphically kinded: :.

    Eq#:. (a:) (b:).(a#b)(ab)

    As with#we usually omit the kind application in as asyntactic convenience.

    The key point is that if : # , then a value Eq# is anordinary term, built with the data constructor Eq#, and having type. Given the GADT () we can define the function castthattakes such a term-level equality witness and casts a value betweenequivalent types:

    cast:(a b:).a(ab)bcast= (a b:) .(x:a) .(eq:ab) .

    caseeqofEq#(c:a#b)xc

    Each use of castforces evaluation of the coercion, via the caseexpression and, in the case of a deferred type error, that is whattriggers the runtime failure.

    Just as cast is a lifted version of, we can lift all the coercioncombinators from the(#)type to(). For example:

    mkRefl:: . (a:).aamkRefl= . (a:) .Eq#a a a

    mkSym :: . (a b:).(ab)(ba)mkSym= . (a b:) .(c:ab) .

    casecofEq#cEq# b a(sym c)

    The relationship between()and(#)is closely analogous to thatbetweenIntandInt#, described twenty years ago in our implemen-tation of arithmetic in GHC (Peyton Jones and Launchbury 1991).Concretely, here is GHCs implementation of addition on Int:

    dataInt=I#Int#

    plusInt::Int Int IntplusInt x y=casexofI#x

    caseyofI#y

    I#(x +#y

    )

    An Intis an ordinary algebraic data type with a single constructorI#(the # is not special; it is just part of the constructor name). Thisconstructor has a single argument, of type Int#, which is the typeofunboxed integers, a honest-to-goodness 32-bit integer value justlike Csint. Finally(+#)is the machine 32-bit addition instruction.We may summarise the relationships thus:

    A value of type Int, or , is always heap-allocated; it isalways represented by a pointer to the heap-allocated object;and the object can be a thunk.

    A value of type Int#, or # is never heap-allocated; and itcannot be a thunk. There is no bottom value of type Int#, or#; we say that they areunlifted types.

    The plusInt function lifts the primitive addition +# from Int#toInt, by explicitly evaluating and unboxing its arguments; andthe functionmkSymworks in just the same way.

    The main difference between Int# and a# b is that the fomeris represented by a 32-bit unboxed value, whereas the latter hasa structure that isirrelevantfor the execution of a program, and canbe represented by a zero-bit value, or entirely erased it comes tothe same thing in the end.

    5. Type inference and deferral of type errors

    With FCin hand we can now explain in more detail the mechanism

    of deferring type errors. We begin by sketching a little more aboutthe type inference process.

    5.1 Type inference by constraint generation

    GHCs type inference algorithm works in two stages (Vytiniotiset al. 2011):

    Step 1: traverse the syntax tree of the input Haskell term, gen-erating type constraints together with an elaborated term3 in

    System FC.

    Step 2: solve the constraints, creating FC bindings that give

    evidence for the solution.

    For example, consider the term show xs, wherexs:[Int], andshow:a.Show aaString. In Step 1 we generate:

    Elaborated term: show[Int]d6xsConstraint: d6: Show[Int]

    The elaborated term looks much like the original except that show

    is now applied to atype argument[Int](corresponding to the ainshows type) and an evidence argument d6(corresponding to theShow a in its type). The constraint is given a fresh name,d6 in this example, which is mentioned in the elaborated term.Afficionados of Haskells type-class system will recognise d6 asshows dictionary argument: it is simply a tuple of functions, themethods of theShowclass.

    When Step 1 is complete, the constraint solver solves the generatedconstraints, producingevidence bindings:

    d6: Show[Int] =$dShowList Int$dShowInt

    Here the solver has constructed a dictionary for Show[Int], usingthe dictionary-construction functions that arise from the instancedeclarations of the class Show:

    $dShowInt : Show Int

    $dShowList : a.Show aShow[a]

    Finally, the evidence bindings are wrapped around the term in a letto make an executable term:

    letd6= $dShowList Int$dShowIntinshow[Int]d6xs

    5.2 Equality constraints

    This is all quite conventional. Somewhat less conventionally (butfollowing the French school of type inference(Pottier and Remy2005)) GHC generates equality constraints as well as type-classconstraints. We simplified the showexample; in reality GHC gen-erates the following:

    Elaborated term: showd6(cast xs c5)Constraints: d6: Showc5:[Int]

    When instantiatingshows type in Step 1, the constraint generatordoes not yet know that it will be applied to the type [Int], soinstead it creates a fresh unification variable , and uses that toinstantiateshows type. Later, when checkingshows argumentx, itmust ensure thatshows argument typeis equal to the actual typeofxs, namely[Int]. It ensures this (eventual) equality by generating

    3 In reality we first generate an elaborated term by decorating the Haskellsource term, andthendesugar it, but we will ignore that extra step here.

  • 7/25/2019 Equality proofs

    6/12

    an equality constraint c5: [Int], again with an arbitrary freshname c5 which names the evidence for the equality. Lastly, inthe elaborated term, we use the cast term cast xs c5 to convert

    xs into a term of type . Notice that c5s type [Int] uses

    the boxed equality()rather than the primitive FC equality(#)(Section 4.2).

    In Step 2, the constraint solver has a little extra work to do: as well

    as solving the constraints and giving bindings for the evidence vari-ables, it must also produce bindings for the unification variables. Inour running example, the solution to the constraints looks like this:

    = [Int]c5:[Int] = mkRefl[Int]

    d6: Show[Int] = $dShowList Int$dShowInt

    The solver decided to eliminate by substituting [Int] for ,the first of the above bindings. The equality c5 now witnessesthe vacuous equality [Int] [Int], but it must still be given abinding, here mkRefl[Int]. (Recall that mkRefl was introduced inSection 4.2.)

    Actually, as a matter of efficiency, in our real implementation theconstraint generator solves many simple and immediately-solubleequalities (such as [Int]) on the fly using a standard unifica-

    tion algorithm, rather than generating an equality constraint to besolved later. But that is a mere implementation matter; the imple-mentation remains faithful to the semantics of generate-and-solve.Moreover, it certainly cannot solve all equalities in this way, be-cause of GADTs and type families (Vytiniotis et al. 2011).

    5.3 Deferring type errors made easy

    In a system generating equality proofs using the () datatype,which has values that can be inhabited by ordinary terms, it is de-lightfully easy to support deferred type errors. During constraintgeneration, we generate a type-equality constrainteven for unifica-tions that are manifestly insoluble. During constraint solving, in-stead of emitting an error message when we encounter an insolu-ble constraint, we emit a warning, and create a value binding for

    the constraint variable, which binds it to a call to error, applied tothe error message string that would otherwise have been emitted atcompile time. And thats all there is to it.

    It is worth noting several features of this implementation technique:

    Each FCterm given aboveis a well-typedF

    Cterm, even though

    some are generated from a type-incorrect Haskell term. Ofcourse it can fail at run-time, but it does so in a civilized way,by raising an exception, not by causing a segmentation fault,or performing (&&) of a character and a boolean. You mightconsider that a program that fails at runtime in this way is notwell-typed, in Milners sense of well typed programs do notgo wrong. But Haskell programs can already go wrong inthis way consider (head[ ]) for example so matters arecertainly no worse than in the base language.

    In short,we have merely deferred the type errors to runtime; wehave not ignored them!

    Deferring type errors is not restricted to interpreted expressionstyped at the interactive GHCi prompt. You can compile anymodule with -fdefer-type-errors and GHC will producea compiled binary, which can be linked and run.

    There is no reflection involved, nor run-time type checking.Indeed there is no runtime overhead whatsoever: the programruns at full speed unless it reaches the point where a runtimeerror is signalled, in which case it halts. (This claim assumesthe optimisations described inSection 7.)

    The technique makes elegant use of laziness. In a call-by-valuelanguage, a strict binding ofcinSection 2.1would be evaluatedby the callfst foo, or even when foois bound, and it would ob-viate the entire exercise if that evaluation triggered the runtimeerror! Nevertheless, the idea can readily be adapted for call-by-value, by simply making()into a sum type:

    dataabwhere

    Eq# :: (a#b)abError:: String ab

    Now, the evidence for an erroneous type constraint would bean Errorvalue, and evaluating that is fine. We simply need toadjustcastto deal with theErrorcase:

    cast= (a b:) .(x:a) .(eq:ab) .caseeqof

    Eq#(c:a#b)xcError serror s

    The technique works uniformly for all type constraints, notonly for equality ones. For example, in Section 5.1, supposethere was no instance for Show[Int]. Then the constraint d6:Show[Int]would be insoluble, so again we can simply emit a

    warning and bind d6to an error thunk. Any program that needsthe evidence ford6will now fail; those that dont, wont.

    We can defer all type errors in terms, but not kind errors intypes. For example, consider

    dataT= MkT(Int Int)

    f(MkT x) =x

    The typeInt Intsimply does not make sense applying InttoIntis a kind error, and we do not have a convenient way to deferkinderrors, only type errors.

    5.4 The placement of errors

    Since many different parts of a program may contribute to a type

    error, there may be some non-determinism about how delayed adeferred type error will be. Suppose that upper:[ Char] [Char],and consider the term:

    upper[ True,a]

    There are two type incompatibilities here. First, the boolean Trueand charactera cannot be in the same list. Second, the functionupperexpects a list of characters but is given a list with a booleanin it. Here is one possible elaboration:

    Elaborated term: upper[ cast True c7,a]Constraints: c7: BoolChar

    But the program could also be elaborated in another way:

    Elaborated term: upper(cast[ True,castac8]c9)Constraints: c8:CharBool

    c9:[Bool][ Char]In this case, type inference has casta to Boolusingc8, so that itcan join True to form a list ofBool; and then cast the list [Bool]to [Char] using c9 to make it compatible with upper. The twoelaborated programs have slightly different runtime behaviour. Ifthe term is bound to tm, then head(tail tm) will run successfully(returningA) in the first elaboration, but fail with a runtime errorin the second.

    We might hope that the type inference engine inserts as few casts aspossible, and that it does so as near to the usage site as possible. Infact this turns out to be the case, because the type inference engine

  • 7/25/2019 Equality proofs

    7/12

    uses the (known) type ofupperto type-check its argument, expect-ing the result to be of type [ Char]. This idea of pushing down theexpected type into an expression, sometimes called bidirectionalor local type inference (Pierce and Turner 2000), is already im-plemented in GHC to improve the quality of error messages; seePeyton Jones et al. (2007,Section 5.4) for details. Although it is aheuristic, and not a formal guarantee, the mechanism localises thecasts very nicely in practice.

    Nevertheless, the bottom line is that the dynamic semantics of atype-incorrect program depends on hard-to-specify implementationdetails of the type inference algorithm. That sounds bad! But wefeel quite relaxed about it:

    The issue arises only for programs that contain errors. Typecorrect programs have their usually fully-specified semantics.

    Regardless of the precise placement of coercions, the elaboratedprogram is type correct. This is not a soundness issue.

    The imprecision of dynamic semantics is no greater a short-coming than the lack of a formal specification of the precisetype error message(s) produced by a conventional type infer-ence engine for a type-incorrect program. And yet no compileror paper known to us gives a formal specification of what type

    errors are produced for a type-incorrect program.

    That said, it is interesting to reflecton approaches that might tightenup the specification, and we do so inSection 9.

    5.5 Summary

    There are many reasons why evidence-based type elaboration, us-ing constraint generation and subsequent constraint solving, is de-sirable (Vytiniotis et al. 2011):

    It isexpressive, readily accommodating Haskells type classes,implicit parameters, and type families.

    It ismodular. The constraintgeneratoraccepts a very large in-put language (all of Haskell), so it has many cases, but each

    case is very simple. In contrast, the constraint solveraccepts avery small input language (the syntax of constraints) but em-bodies a complex solving algorithm. Keeping the two separateis incredibly wonderful.

    It is robust: for example it does not matter whether the con-straint generator traverses the term left-to-right or right-to-left:the resulting constraint is the same either way.

    Neither the constraint generator nor the solver need be trusted; avery simple, independent checker can type-check the elaborated

    FCterm.

    To this list we can now add a completely new merit: it is dead easyto implement deferred type errors, a feature that is desired by manyHaskell programmers.

    6. Discussion

    Now that we have discussed type inference in more detail, we pauseto reflect on our design choices.

    6.1 Evidence uniformity

    Weve seen that deferring type errors provides a good motivationfor treating coercions as term-level constructs. But there is anotherway in which treating coercions as values turns out to be veryconvenient. In the Haskell source language, equality constraints

    are treated uniformly with type-class constraints and implicit-parameter constraints; anywhere a class constraint can appear,an equality constraint can appear, and vice versa. Class constraintsand implicit-parameter constraints definitely cannot be erased: bydesign their evidence carries a runtime value. Treating some con-straints as non-erasable values and others (the equalities) as type-like, erasable constructs, led to many annoying special cases in the

    type inference and source-to-F

    Celaboration of Haskell programs.The most troublesome example of this non-uniformity arises whentreating Haskells superclasses. Consider the declaration

    class(aF b,Eq a)C a bwhere . . .

    HereEq a is a superclass ofC a b, meaning that from evidence forC a bone can extract evidence for Eq a. Concretely this extractionis witnessed by a field selector:

    sc2:C a bEq a

    which takes a dictionary (i.e. record of values) forC a b and picksout theEq afield. In just the same way one should be able to extractevidence foraF b, which suggests a selector function with type

    sc1:C a b(aF b)

    Before we distinguished () and (#

    ) we could not write this

    function because there simply is no such function in FC; indeedthe type C a b (a# F b) is not even well kinded in Figure 3.There is a good reason for this: dictionaries can be recursivelydefined and can diverge(Lammel and Peyton Jones 2005), so theselector function may diverge when evaluating its arguments butthe typea# F bcannot represent divergence, because that wouldbe unsound.

    The tension is readily resolved by (); the type of sc1 is wellformed and its definition looks something like this:

    sc1= ab .(d:C a b) .casedof

    MkC(c:a#F b) (eq:Eq a) . . . Eq#c

    This accumulation of infelicities led us to the two-equality plan,

    and in fact we had fully implemented this design even before weever thought of deferring type errors. Now, we get the ability todefer errors regarding type unification, missing class constraints,and implicit parameters, all in one go.

    6.2 Why kind polymorphism is important

    We have mentioned that both equalities (#) and () are kind-polymorphic, but we have not yet said why. Right from the begin-ning Haskell has featured kinds other than, such as . Duringtype inference, when unifying, say, Maybe Int, the inferenceengine or, more precisely, the constraint solver must decom-pose the equality to give Maybe and Int. The former equal-ity is at kind , so it follows that the()type constructor itselfmust be either (a) magically built in or (b) poly-kinded. And simi-larly(#).

    Solution (a) is entirely possible: we could add and #tothesyntax of types,and give them their own kinding rules. But thereare unpleasant knock-on effects. The optimizer would need to betaught how to optimize terms involving(). Worse, it turns out thatwe need equalities between equalities, thus (1 1) (2 2),which in turn leads to the need for new coercion combinators todecompose such types.

    Happily there are many other reasons for wanting kind polymor-

    phism in FC(Yorgey et al. 2012), and once we have kind polymor-

    phism we can readily make the equality type constructors kind-polymorphic.

  • 7/25/2019 Equality proofs

    8/12

    6.3 What the Haskell programmer sees

    A salient feature of Haskells qualified types (type classes, implicitparameters, equality constraints) is that the type inference enginefills in the missing evidence parameters. So iffhas type

    f:: (Num b,aF b)abb

    then given a source-language call(f e1e2), the type inference willgenerate the elaborated call(f d c e1e2), whereand are thetypes that instantiate a and b, and dand c are evidence terms thatwitness thatNumholds and that F, respectively.

    One might wonder whether one can (in Haskell) also write

    g :: (a#F b)abb

    and have the above evidence-generation behaviour. No, you cannot.The whole point of the ()type is that it can be treated uniformlywith other evidence (bound in letrec, returned as a result of a call),whereas (#) cannot. So in thesourcelanguage #isnota typeconstraint you can write before the in a Haskell type, and haveit participate in constraint solving. The entire constraint generationand solving process works exclusively with well-behaved, uniform,boxed constraints. Only when constraint solving is complete does(#)enter the picture, as we discuss next.

    7. Optimizing equalities

    We now have a correct implementation, but it looks worryinglyexpensive. After all, the constraint generation/solving process maygenerate a program littered with coercion bindings and casts, allof which are invisible to the programmer, have no operationalsignificance, and merely ensure that well typed programs dontgo wrong. Yet each will generate a heap-allocated Eq#box, readyto be evaluated by cast. Happily, almost all of these boxes areeliminated by existing optimizations within GHC, as this sectiondescribes.

    7.1 Eliminating redundant boxing and unboxing

    The fact that we have defined () as an ordinary GADT meansthat is fully exposed to GHCs optimizer. Consider a Haskell 98program that turns out to be well typed. The constraint generatorwill produce many constraints that are ultimately solved by reflex-ivity, because the two types really are equal. Here is a typical caseof an elaborated term:

    let(c:CharChar) =mkRefl Charin . . .(cast e c) . . .

    (Recall that mkRefl and cast were defined in Section 4.2.) As itstands, theletwill heap-allocate a thunk which, when evaluated bythecast, will turn out to be anEq#constructor wrapping a reflexivecoercionChar. All this is wasted work. But GHCs optimizer caninline the definitions ofmkRefland castfrom Section4.2to get

    let(c:CharChar) =Eq# Char CharCharin . . .(casecofEq#c

    ec) . . .

    Now it can inlinecat its use site, and eliminate the case expression,giving

    . . .(eChar) . . .

    Remembering that primitive casts () can be erased, we have elim-inated the overhead. Moreover, the optimizations involved have allbeen in GHC for years; there is no new special purpose magic.

    What happens when a deferred type error means that a cast cannot,

    and should not, be erased? Consider once more the FCterm

    let(c:CharBool) =error"Couldnt match..."insnd(True,(castac)&&False)

    Now, simply by inlining castand c, the optimizer can transform to

    snd(True,(caseerror"..."of{Eq#cac})&&False)

    After inlining(&&), and simplifyingcase-of-errorto just a call of

    error, both standard transformations in GHCs optimizer) we get

    snd(True,error"...")

    Even erroneous programs are optimized by removing their deadcode! The point is this: by exposing the evaluation of coercions, weallow theexistingoptimizer transformations to work their magic.

    7.2 Equalities and GADTs

    Let us reconsider the GADT example given inSection 3.2:

    dataT awhere

    T1 ::IntT IntT2 :: a T a

    There we said that the constructor T1 is typed thus:

    T1:a.(a#Int)Int T a

    That is true in System FC. But the Haskell programmer, who knows

    only of the type , considersT1to have this type:

    T1 :: a.(aInt)Int T a

    It would be perfectly sound to adopt the latter type for T1 inthe elaborated program; for example, function f fromSection 3.2would be elaborated thus:

    f= a .(x:T a) .casexof

    T1(c:aInt) (n:Int)cast(Cons(n + 1)Nil) (mkSym[ c])

    T2vNil

    Since an argument of type a Inthas a lifted type with a boxedrepresentation, it would take up a whole word in every T1 object.Moreover, sincec is bound by the pattern match, the caseexpres-sion inmkSymwill not cancel with an Eq# box in the binding for c.This is not good! What has become of our zero-overhead solution?

    The solution is simple: we desugar GADTs to contain unlifted,rather than lifted, equalities. We can do this in such a way thatthe Haskell programmer still sees only the nice well-behaved()types, as follows. First, in the elaborated program the type ofT1is:

    T1:a.(a#Int)Int T a

    However, the elaborator replaces every source-language call ofT1with a call of a constructor wrapper function, T1wrap, defined likethis:

    T1wrap:a.(aInt)IntT aT1wrap= (a:) .(c:aInt) .(n:Int) .

    casecofEq#c1 T1c1n

    The wrapper deconstructs the evidence and puts the payload intoT1 where, since it is erasable, it takes no space.

    Dually, a source-program pattern match is elaborated into a FC

    pattern match together with code to re-box the coercion. So ourfunctionfis elaborated thus:

    f= a .(x:T a) .casexof

    T1(c1: a#Int) (n:Int)

  • 7/25/2019 Equality proofs

    9/12

    letc=Eq#c1 -- Re-boxingincast(Cons(n + 1)Nil) (mkSym[ c])

    T2vNil

    Now the earlier optimizations will get rid of all the boxing and un-boxing and we are back to nice, efficient code. The technique of un-boxing strict arguments on construction, and re-boxing on patternmatching (in the expectation that the re-boxing will be optimized

    away) is precisely what GHCs UNPACKpragma on constructor ar-guments does. So, once more, coercions can hitch a free ride onsome existing machinery.

    7.3 How much is optimized away?

    We have argued that the boxing and unboxing, introduced in theelaborated program by the type checker, will largely be eliminatedby standard optimizations. But not always! Indeed that is the point:the main reason for going to all of this trouble is to handle smoothlythe cases (deferred type errors, recursive dictionaries) when equal-ities cannot, and should not, be erased. But still, one might reason-ably ask, can we offer any guarantees at all?

    Consider a type-correct Haskell program that contains (a) no equal-ity superclasses, and (b) no functions that take or return a value

    of type , apart from GADT constructors. This includes allHaskell 98 programs, and all GADT programs. After typechecking

    and elaboration to FC, suppose that we inline every use ofmkRefl,

    mkSym, etc, and the GADT constructor wrappers. Then

    Every use of a variable of type will be a case expressionthat scrutinises that variable, namely the unboxingcaseexpres-sions inmkRefl,mkSym, etc, and GADT constructor wrappers.

    Every binding of an evidence variable of type will be aletwhose right hand side returns a statically-visibleEq# box.

    By inlining theselet-bound evidence variables at their use sites, wecan cancel thecasewith theEq# constructors, thereby eliminatingall boxing and unboxing. To illustrate, consider once more theelaboration of function fat the end of the previous subsection. Ifwe inlinemkSymandcastwe obtain:

    f= a .(x:T a) .casexof

    T1(c1: a#Int) (n:Int)letc =Eq#c1in -- Re-boxing

    letc2=casecofEq#c Eq#(sym[ c

    ])in casec2of

    Eq#c2 Cons(n + 1)Nilc

    2

    T2vNil

    The right hand side ofc 2 comes from inlining mkSym, while thecasec2 . . . comes from inlining cast. Now we can inline c incasec . . ., and c2 in casec2 . . ., after which the cancellationof boxing and unboxing is immediate.

    When does this notwork? Consider exception (b) above, where aprogrammer writes a function that is explicitly abstracted over an

    equality:

    f :a.F a Int[ F a ]Intf x=head x + 1

    The elaborated form will look like this:

    f :a.F a Int[ F a ]Intf= a .(c:F aInt) .(x:[ F a ]) .

    head(cast x c) + 1

    Sincecis lambda-bound, there is no Eq#box for thecastto cancelwith. However we can perform the same worker/wrapper split tothis user-defined function that we did for constructors, thus

    f :a.F aInt[ F a ]Intf= a .(c:F a Int) .(x:[ F a ]) .

    casecofEq#c fwrk c x

    fwrk:a.F a #Int[ F a ]Intfwrk= a .(c :F a#Int) .(x:[ F a ]) .

    letc=Eq#c inhead(cast x c) + 1

    Now in fwrk the boxing and unboxing cancels; and dually we arefree to inline the wrapper function f at its call sites, where theunboxing will cancel with the construction. This worker/wrappermechanism is precisely what GHC already implements to eliminateboxing overheads on strict function arguments(Peyton Jones andSantos 1998), so it too comes for free. There isa small price to pay,however: the transformation makes the function strict in its equalityevidence, and that in turn might trigger a deferred error messageslightly earlier than strictly necessary. In our view this is a trade-offworth making. In our current implementation the worker/wrappertransform on equalities is only applied when the function reallyis strict in the equality; we have not yet added the refinement ofmakingit strict in all equalities.

    7.4 Summary

    In short, although we do not give a formal theorem (which wouldinvolve formalizing GHCs optimizer) we have solid grounds,backed by observation of optimized code, for stating that the uni-form representation of equality evidence can be successfully opti-mized away in all but the cases in which it cannot and should not beeliminated, namely for deferred type errors and functions that mustaccept or return term-level equalities (such as selectors for equalitysuperclasses). Of course, introducing and then eliminating all theseboxes does mean a lot more work for the compiler, but this has notproved to be a problem in practice.

    8. Related work

    8.1 Relation to hybrid and gradual typing

    Hybrid type checking(Flanagan 2006)refers to the idea of defer-ring statically unprovedgoals as runtime checks. Achieving simi-lar effects, but arriving from the opposite end of the spectrumsofttyping tries to elimiminate statically as many runtime checks aspossible (Wright and Cartwright 1997). There are two differencescompared to our approach: First, in hybrid type checking typicallyonly unsolved goals will be deferred whereas insoluble ones will bereported statically. In our case, insoluble and unsolved goals will bedeferred. Second, unsolved goals will be deferred as checksin hy-brid type systems, whereas they will remain errors in our system:Our treatment of coercions does not replace static type errors byruntime checks, but rather delaystriggering a static error until theoffending part of the program is evaluated, at runtime. For instance,consider the following program, which contains a static error but is

    compiled with-fdefer-type-errors:f:: a.aaaf x y=x&&y

    There is a static error in this program because f is supposed tobe polymorphic in the type of its arguments x and y, which arenevertheless treated as having type Bool. At runtime, even if weevaluate the application of fon arguments of type Bool, such as

    f True Falsewe will get a type error Couldnt match type a withBool, despite the fact that the arguments of f areof type Bool atruntime. In contrast, a truly dynamic type-check would not triggera runtime error.

  • 7/25/2019 Equality proofs

    10/12

    In the context of GHC, there is no straightforward way to incor-porate runtime checks instead of error triggers at runtime, unlessdynamic type information is passed along with polymorphic types.Though systems with dynamic types and coercions have been stud-ied in the literature (Henglein 1994), we have not examined thispossibility.

    There is a large body of work on interoperating statically and dy-

    namically typed parts of a program, often referred to as gradualtyping(Ahmed et al. 2011;Siek and Taha 2006;Siek and Vachhara-jani 2008; Tobin-Hochstadt and Felleisen 2006). Typically, stat-ically insoluble goals will be reported statically (Siek and Taha2006), whereas insoluble goals which perhaps arise from the po-tential interoperation of dynamic and static parts of the programwill be wrapped with runtime checks. The main problem in grad-ual typing is to identify how to assign blame in case of a contractviolation (or a dynamic type error). We have seen in Section 5.3that non-determinism in the dynamic placement of the type errormay well exist. Consequently it might be an interesting avenue toexplore if ideas from gradual typing could help us specify a morepredictable blame assignment for these deferred type errors. Notefinally that previous work on type error slicing ( Haack and Wells2004) has focused on identifying parts of a program that contributeto a type error and would be potentially useful for reducing thisnon-determinism both for static error messages and for better spec-ifying the dynamic behaviour of an erroneous program.

    8.2 Deferring type errors

    DuctileJ is a plugin to the Java compiler that converts a normal Javaprogram to one in which type checking is deferred until runtime(Bayne et al. 2011). The authors provide an extensive discussion ofthe software engineering advantages of deferring type errors, undertwo main headings.

    During prototyping, programmers often comment out partlywritten or temporarily out-of-date code, while prototypingsome new part. Commenting out is tiresome because one mustdo it consistently: if you comment outfyou must comment outeverything that callsf, and so on. Deferring type errors is a kind

    of lazy commenting-out process.

    During software evolution of a working systemit can be burden-some to maintain global static type correctness. It may be moreproductive to explore a refactoring, or change of data represen-tation, in part of a software system, and test that part, withoutcommitting to propagating the change globally.

    We urge the reader to consult this excellent paper for a full exposi-tion, and a good evaluation of the practical utility of deferring typeerrors both during prototyping and for software evolution.

    Note however that although our motivations are similar, our imple-mentation differs radically from that in DuctileJ. The latter worksby a de-typing transformation that uses Javas runtime type infor-mation and reflection mechanisms to support runtime type checks.This imposes a significant runtime cost the paper reports a slow-

    down between 1.1 and 7.8 times. In contrast, our implementationperforms no runtime reflection and runs at full speed until the typeerror itself is encountered. The DuctileJ de-typing transformation isalso not entirely faithful to the original semantics unsurprisingly,the differences involve reflection whereas ours is fully faithful,even for programs that involve Haskells equivalent of reflection,the Typeable and Data classes. To be fair, many of the DuctileJcomplexities go away because Haskell is a simpler language thanJava. Further, GHC already implemented (the quite advance fea-tures of) kind polymorphism, coercions, and unboxing, which al-lowed us to piggyback-fdefer-type-errors onto the existingcompiler implementation with very little effort.

    Deferring type errors is also a valuable tool in the context of IDEdevelopment. In an IDE it is essential to provide feedback to theprogrammer even if the program has errors. The Visual Basic IDEuses a hierarchy of analysis to provide gradually more functionalitydepending on the type of errors present (Gertz 2005). For instance,if there are type errors, but no parse errors, smart indentation andautomatic code pretty-printing can already be applied. However,more complicated refactorings require type information to be avail-able. Some IDEs use error-correcting parsers to be able to providesome functionality in the presence of parsing errors, but a type errorwill require a correction by the user before the IDE can offer func-tionality that depends on the availability of types. Deferring typeerrors allows the compiler to complete type checking without fix-ing the type errors, allowing for a Haskell IDE to remain functionaleven for programs that do not type-check.

    8.3 Proof erasure

    Coq (The Coq Team)usesa sort-basederasure process by introduc-ing a special universe for propositions,Prop, which is analogous toour Constraint# kind. Terms whose type lives in Prop are erasedeven when they are applications of functions (lemmas) to computa-tional terms. This is sound in Coq, since the computation language

    is also strongly normalizing. Extending the computation languageof F

    C proofs or finding a way to restrict the ordinary computation

    language of FC using kinds in order to allow it to constructprim-

    itive equalities is an interesting direction towards true dependenttypes for Haskell.

    Irrelevance-basederasure is another methodology proposed in thecontext of pure type systems and type theory. In the context of Epi-gram, Brady et al. (2003) presented an erasure technique whereterm-level indices of inductive types can be erased even when theyare deconstructed inside the body of a function, since values of theindexed inductive datatype will be simultaneously deconstructedand hence the indices are irrelevant for the computation. In theAgda language (Norell 2007) there exist plans to adopt a similarirrelevance-based erasure strategy. Other related work(Abel 2011;Mishra-Linger and Sheard 2008) proposes erasure in the contextof PTSs guided with lightweight programmer annotations. Therealso exist approaches that lie in between sort-based erasure andirrelevance-based erasure: for instance, inimplicit calculus of con-structions (Miquel 2001)explicitly marked static information (notnecessarily Prop-bound) does not affect computation and can be

    erased (Barras and Bernardo 2008). In FC the result of a computa-

    tion cannot depend on the structure of an equality proof, by con-struction: there is no mechanism to decompose the structure of acoercion at all at the term-level. Hence a coercion value needs nostructure (since it cannot be decomposed), which allows us to per-form full erasure without any form of irrelevance analysis.

    This idea of separating the computational part of a proof-likeobject, which always has to run before we get to a zero-cost log-ical part is reminiscent of a similar separation that A-normal

    forms introduce in refinement type systems, for instance(Bengt-son et al. 2008) or the more recent work on value-dependenttypes(Borgstrom et al. 2011;Swamy et al. 2011). This line of workseems the closest in spirit to ours, with similar erasure concerns,and there is rapidly growing evidence of the real-world potentialof these ideas see for instance the discussion and applicationsreported bySwamy et al.(2011).

    9. Future work and conclusions

    Error coercion placement This paper has been about an imple-mentation technique that uses first-class proof-like objects to al-

  • 7/25/2019 Equality proofs

    11/12

    low for deferred type errors with very low overhead. A natural nextstep would be towards a declarative specification of the elabora-tion process from source to a target language which specifies theplacement of the deferred error messages on potentially erroneoussub-terms. Recent related work on coercion placement in the con-text of coercive subtyping is the work ofLuo (2008) andSwamyet al. (2009); these would certainly be good starting points for in-vestigations on a declarative specification of deferring type errors.The canonical reference for coercion placement in a calculus withtype-dynamic is the work ofHenglein (1994), but it seems some-what disconnected from our problem as we do not have currentlyany way of dynamically passing type information or executing pro-grams that contain static errors but are safe dynamically.

    In general, this problem seems very hard to tackle without ex-posing some of the operation of the underlying constraint solver.In the other direction, a principled approach to deferring type er-rors might actually provide guidance on the order in which con-straints should be solved. For instance,when solving theconstraintsC1 C2 C3 arising from the expressionse1,e2, ande3 in the termife1 thene2 elsee3, we might want to prioritise solving the con-straint C1. In this way, if an error is caused by the interaction of theexpressionse2 or e3 withe1, we would still be able to execute the

    condition of the branch e1 before we emit a deferred type error fore2 or e3. Otherwise we run the danger of the terme2 or e3 forcingsome unification that makes constraintC1 insoluble, giving rise toan earlier error message (during evaluation of the condition terme1). However, it is not clear what should happen when C2 andC3have a common unification variable, and there is freedom in de-ferring either one, for instance. Therefore this problem is certainlyworth further investigation.

    The equality type Internalizing definitional equality (#) as atype()is pretty standard in languages with dependent types ( Li-cata and Harper 2005). For example, programming with first-classequality witnesses is sometimes convenient to avoid shortcomingsof implementations of dependent pattern matching.

    Recent work on higher-dimensional type theory(Licata and Harper2012) goes one step further to show that the () datatype can beextendedwith yet another constructor for term-level isomorphismsbetween types. Interestingly the usual definitional equality infer-ence rules apply for this extended equality type. Moreover theyshow that the term language can be equipped with an equationaltheory that is rich enough, so that types enjoy canonical forms. Ofcourse the language they address is simpler in some respects (nopartiality or divergence, no polymorphism), nor is there a reductionsemantics. In a real compiler, being able to extend the ()datatypewith true computational isomorphisms and maintain soundness andproviding a transparent treatment of these isomorphisms with min-imal programmer intervention is an interesting direction for futureresearch.

    Conclusions In this paper we have proposed a simple and light-weight mechanism for deferring type errors, in a type-safe way thatrequires no program rewriting, and preserves the semantics of theprogram until execution reaches the part that contains a type error.We have shown that this can be done in an entirely safe way in thecontext of a typed intermediate language, and in fact without re-

    quiring any modifications to System FC or the compiler optimizer.

    This work is fully implemented in GHC, where it has in additiongreatly simplified the implementation of type inference and elabo-

    ration of Haskell to FC.

    Acknowledgments

    Particular thanks are due to Etienne Laurin, who suggested to usthe idea of deferring type errors (GHC Tracticket 5624), althoughhis implementation was very different to the one described here.We thank Stephanie Weirich and Brent Yorgey for their detailedcomments and suggestions, and the ICFP reviewers for the helpfulfeedback.

    This work has been partially funded by the Portuguese Foundationfor Science and Technology (FCT) via the SFRH/BD/35999/2007grant, and by EPSRC grant number EP/J010995/1.

    References

    Andreas Abel. Irrelevance in type theory with a heterogeneous equalityjudgement. In Foundations of Software Science and ComputationalStructures, 14th International Conference, FOSSACS 2011, pages 5771. Springer, 2011.

    Amal Ahmed, Robert Bruce Findler, Jeremy G. Siek, and Philip Wadler.Blame for all. In Proceedings of the 38th annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages , POPL11, pages 201214, New York, NY, USA, 2011. ACM. ISBN 978-1-4503-0490-0. doi: 10.1145/1926385.1926409.

    Bruno Barras and Bruno Bernardo. The implicit calculus of constructionsas a programming language with dependent types. In Foundations ofSoftware Science and Computation Structure, pages 365379, 2008. doi:10.1007/978-3-540-78499-9 26.

    Michael Bayne, Richard Cook, and Michael Ernst. Always-available staticand dynamic feedback. InProceedings of 33rd International Conferenceon Software Engineering (ICSE11), pages 521530, Hawaii, 2011.

    Jesper Bengtson, Karthikeyan Bhargavan, Cedric Fournet, Andrew D. Gor-don, and Sergio Maffeis. Refinement types for secure implementations.In Proceedings of the 2008 21st IEEE Computer Security FoundationsSymposium, pages 1732, Washington, DC, USA, 2008. IEEE ComputerSociety. ISBN 978-0-7695-3182-3.

    Johannes Borgstrom, Juan Chen, and Nikhil Swamy. Verifying statefulprograms with substructural state and Hoare types. In Proceedings ofthe 5th ACM Workshop on Programming Languages meets ProgramVerification, PLPV 11, pages 1526, New York, NY, USA, 2011. ACM.ISBN 978-1-4503-0487-0.

    Ana Bove, Peter Dybjer, and Ulf Norell. A brief overview of Agda afunctional language with dependent types. InTPHOLs 09: Proceedingsof the 22nd International Conference on Theorem Proving in HigherOrder Logics, pages 7378, Berlin, Heidelberg, 2009. Springer-Verlag.

    Edwin Brady, Conor McBride, and James McKinna. Inductive familiesneed not store their indices. In Stefano Berardi, Mario Coppo, andFerruccio Damiani, editors, TYPES, volume 3085 ofLecture Notes inComputer Science, pages 115129. Springer, 2003.

    Manuel M. T. Chakravarty, Gabriele Keller, and Simon Peyton Jones. As-sociated type synonyms. In ICFP 05: Proceedings of the Tenth ACMSIGPLAN International Conference on Functional Programming, pages241253, New York, NY, USA, 2005. ACM.

    James Cheney and Ralf Hinze. First-class phantom types. CUCIS TR2003-1901, Cornell University, 2003.

    Cormac Flanagan. Hybrid type checking. In Proceedings of the 33rdACM SIGPLAN-SIGACT Symposium on Principles of Programming

    Languages, POPL 06, pages 245256, New York, NY, USA, 2006.ACM. doi: 10.1145/1111037.1111059.

    Matthew Gertz. Scaling up: The very busy background compiler. MSDNMagazine, 6 2005. URL http://msdn.microsoft.com/en-us/magazine/cc163781.aspx.

    Christian Haack and J. B. Wells. Type error slicing in implicitly typedhigher-order languages. Science of Computer Programming, 50:189224, March 2004. ISSN 0167-6423.

    Fritz Henglein. Dynamic typing: syntax and proof theory. Science ofComputer Programming, 22:197230, June 1994. ISSN 0167-6423.

    http://hackage.haskell.org/trac/ghc/ticket/5624http://msdn.microsoft.com/en-us/magazine/cc163781.aspxhttp://msdn.microsoft.com/en-us/magazine/cc163781.aspxhttp://msdn.microsoft.com/en-us/magazine/cc163781.aspxhttp://msdn.microsoft.com/en-us/magazine/cc163781.aspxhttp://msdn.microsoft.com/en-us/magazine/cc163781.aspxhttp://hackage.haskell.org/trac/ghc/ticket/5624
  • 7/25/2019 Equality proofs

    12/12

    Oleg Kiselyov, Simon Peyton Jones, and Chung-chieh Shan. Fun withtype functions. In A.W. Roscoe, Cliff B. Jones, and Kenneth R.Wood, editors, Reflections on the Work of C.A.R. Hoare, History ofComputing, pages 301331. Springer London, 2010. doi: 10.1007/978-1-84882-912-1 14.

    Ralf Lammel and Simon Peyton Jones. Scrap your boilerplate with class:extensible generic functions. InProceedings of the 10th ACM SIGPLAN

    International Conference on Functional Programming, ICFP 05, pages

    204215, New York, NY, USA, 2005. ACM. doi: 10.1145/1086365.1086391.

    Daniel R. Licata and Robert Harper. A formulation of dependent ML withexplicit equality proofs. Technical Report CMU-CS-05-178, CarnegieMellon University Department of Computer Science, 2005.

    Daniel R. Licata and Robert Harper. Canonicity for 2-dimensional typetheory. In Proceedings of the 39th annual ACM SIGPLAN-SIGACTSymposium on Principles of Programming Languages, POPL 12, pages337348, New York, NY, USA, 2012. ACM. doi: 10.1145/2103656.2103697.

    Zhaohui Luo. Coercions in a polymorphic type system. MathematicalStructures in Computer Science, 18(4):729751, August 2008. ISSN0960-1295. doi: 10.1017/S0960129508006804.

    Alexandre Miquel. The implicit calculus of constructions: extending puretype systems with an intersection type binder and subtyping. In Pro-ceedings of the 5th International Conference on Typed Lambda Calculi

    and Applications, TLCA01, pages 344359, Berlin, Heidelberg, 2001.Springer-Verlag. ISBN 3-540-41960-8.

    Nathan Mishra-Linger and Tim Sheard. Erasure and polymorphism inpure type systems. In Roberto Amadio, editor, Foundations of SoftwareScience and Computational Structures, volume 4962 ofLecture Notes inComputer Science, pages 350364. Springer Berlin / Heidelberg, 2008.

    Ulf Norell.Towards a practical programming language basedon dependenttype theory. PhD thesis, Department of Computer Science and Engineer-ing, Chalmers University of Technology, September 2007.

    Simon Peyton Jones and John Launchbury. Unboxed values as first classcitizens in a non-strict functional programming language. InFPCA91:Conference on Functional Programming Languages and Computer Ar-chitecture, pages 636666, New York, NY, August 1991. ACM Press.

    Simon Peyton Jones and Andre Santos. A transformation-based optimiserfor Haskell. Science of Computer Programming, 32(1-3):347, Septem-ber 1998.

    Simon Peyton Jones, Dimitrios Vytiniotis, Stephanie Weirich, and GeoffreyWashburn. Simple unification-based type inference for GADTs. InProceedings of the 11th ACM SIGPLAN International Conference onFunctional Programming, pages 5061, New York, NY, USA, 2006.ACM Press. ISBN 1-59593-309-3.

    Simon Peyton Jones, Dimitrios Vytiniotis, Stephanie Weirich, and MarkShields. Practical type inference for arbitrary-rank types. Jour-nal of Functional Programming, 17(01):182, 2007. doi: 10.1017/S0956796806006034.

    Benjamin C. Pierce and David N. Turner. Local type inference.ACM Trans-actions on Programming Languages and Systems, 22(1):144, January

    2000. ISSN 0164-0925. doi: 10.1145/345099.345100.

    Francois Pottier and Didier Remy. The essence of ML type inference. InBenjamin C. Pierce, editor,Advanced Topics in Types and Programming

    Languages, chapter 10, pages 389489. MIT Press, 2005.

    Tim Sheard and Emir Pasalic. Meta-programming with built-in type equal-ity. In Proc 4th International Workshop on Logical Frameworks and

    Meta-languages (LFM04), pages 106124, July 2004.

    Jeremy G. Siek and Walid Taha. Gradual typing for functional languages. InScheme and Functional Programming Workshop, pages 8192, Septem-ber 2006.

    Jeremy G. Siek and Manish Vachharajani. Gradual typing with unification-based inference. In Proceedings of the 2008 symposium on Dynamiclanguages, DLS 08, pages 7:17:12, New York, NY, USA, 2008. ACM.doi: 10.1145/1408681.1408688.

    Martin Sulzmann, Manuel M. T. Chakravarty, Simon Peyton Jones, andKevin Donnelly. System F with type equality coercions. In Proceedingsof the 2007 ACM SIGPLAN Workshop on Types in Language Design and

    Implementation, pages 5366, New York, NY, USA, 2007. ACM.

    Nikhil Swamy, Michael Hicks, and Gavin M. Bierman. A theory of typedcoercions and its applications. In Proceedings of the 14th ACM SIG-PLAN International Conference on Functional Programming, ICFP 09,pages 329340, New York, NY, USA, 2009. ACM. ISBN 978-1-60558-332-7.

    Nikhil Swamy, Juan Chen, Cedric Fournet, Pierre-Yves Strub, KarthikeyanBharagavan, and Jean Yang. Secure distributed programming withvalue-dependent types. In Proceedings of the 16th ACM SIGPLAN

    International Conference on Functional Programming, ICFP11, pages266278. ACM, September 2011. doi: 10.1145/2034773.2034811.

    The Coq Team. Coq. URLhttp://coq.inria.fr.

    Sam Tobin-Hochstadt and Matthias Felleisen. Interlanguage migration:from scripts to programs. In Companion to the 21st ACM SIGPLANSymposium on Object-Oriented Programming Systems, Languages, and

    Applications, OOPSLA 06, pages 964974, New York, NY, USA, 2006.ACM. doi: 10.1145/1176617.1176755.

    Dimitrios Vytiniotis, Simon Peyton Jones, Tom Schrijvers, and MartinSulzmann. OutsideIn(X): Modular Type inference with local assump-tions. Journal of Functional Programming, 21, 2011.

    Stephanie Weirich, Dimitrios Vytiniotis, Simon Peyton Jones, and SteveZdancewic. Generative type abstraction and type-level computation. In

    Proceedings of the 38th annual ACM SIGPLAN-SIGACT Symposium onPrinciples of Programming Languages, POPL 11, pages 227240, NewYork, NY, USA, 2011. ACM. ISBN 978-1-4503-0490-0.

    Andrew K. Wright and Robert Cartwright. A practical soft type system forscheme. ACM Transactions on Programming Languages and Systems,19(1):87152, January 1997. ISSN 0164-0925. doi: 10.1145/239912.239917.

    Brent A. Yorgey, Stephanie Weirich, Julien Cretin, Simon Peyton Jones,Dimitrios Vytiniotis, and Jose Pedro Magalhaes. Giving Haskell apromotion. In Proceedingsof the8th ACM SIGPLAN Workshop on Typesin Language Design and Implementation, TLDI 12, pages 5366, NewYork, NY, USA, 2012. ACM. doi: 10.1145/2103786.2103795.

    http://coq.inria.fr/http://coq.inria.fr/http://coq.inria.fr/