4.5 Conclusion
5.1.1 Features of Pico
Pico is a dependently typed λ-calculus with mutually recursive algebraic datatypes
and a fixpoint operator. Recursion is modeled only via this fixpoint operator; there is no recursive let. Other than the way in which the operational semantics deals with coercions in the form ofpush rules, the small-step semantics is what you might expect for a call-by-name λ-calculus.
The typing relations, however, have a few features worth mentioning up front (other unusual features are best explained after the detailed coverage of Pico; see
Section 5.12).
39There is a coercion form that starts withλ; it is only a congruence form forλ-abstractions in
types, not aλ-abstraction in the coercion language. See Section 5.8.5.1.
5.1.1.1 Relevance annotations and type erasure
A key concern when compiling a dependently typed language is type erasure. Given that terms and types can intermingle, what should be erased during compilation? And what data is necessary to be retained until runtime? Dependent Haskell (and, in turn,Pico) forces the user to specify this detail at each quantifier (Section 5.3). In the formal grammar of Pico, we distinguish between Πa:Relκ. ...and Πa:Irrelκ. .... The
former is the type of an abstraction that is retained at runtime, written with a Π in Haskell; the latter, written with∀, is fully erased. In order to back up this claim of full erasure of irrelevant quantification, evaluation happens under irrelevant abstractions; see Section 5.7.1.
So that we can be sure a variable’s relevance is respected at use sites, variable contexts Γtrack the relevance of bound variables. Only relevant variables may appear in the “level” in which they were bound; when a typing premise refers to a higher “level”, the context is altered to mark all variables as relevant. For example, the case construct caseκτofalt includes the return kind of the entire case expression as its κ
subscript. This kind is type-checked in a context where all variables are marked as relevant; because the kind is erased during compilation, the use of an irrelevant variable there is allowed. As they are also erased, coercions are considered fully irrelevant as well.
My treatment of resetting the context is precisely like what is done by Mishra-Linger and Sheard [65].
5.1.1.2 Tracking matchable vs. unmatchable functions
Dependent Haskell supports both matchable—that is, generative and injective— abstractions and unmatchable ones (Section 4.2.4). Though at first it might appear that separating out these two modalities is necessary only to support type inference,
Pico maintains this distinction. Every Π-type in Picois labeled as either matchable
or unmatchable: ’Π denotes a matchable Π-type and
˜
Π denotes an unmatchable one. An unadorned Π is a metavariable which might be instantiated either to ’Π or
˜ Π. We do not have to label λ-abstractions, however, because all λ-abstractions are always unmatchable—only partially applied type constants (or functions returning them) are matchable.
Pico maintains the matchable/unmatchable distinction for two reasons:
Decomposing coercions over function applications Since at least the invention of System FC [87], GHC has supported application decomposition. That is, from a proof that τ1σ1 equals τ2σ2, we can derive proofs ofτ1 ∼ τ2 and σ1 ∼ σ2. I would like
to retain this ability in Pico in order to support the claim that Dependent Haskell is a conservative extension over today’s Haskell. However, decomposing an application
as above in the presence of unsaturated λ-abstractions is clearly bogus.41
The solution here is to keep matchable applications separate from unmatchable ones, and allow decomposition only of matchable applications. The two application forms comprise different nodes in the Picogrammar. Decomposing only matchable applications is a backward-compatible treatment, as today’s Haskell has only matchable applications. In turn, keeping the application forms separate requires tracking the matchability of the abstractions themselves.
Pico’s support of the application decomposition while allowing unsaturated λ-
abstractions is one of the key improvements Pico makes over Gundry’s evidence
language [37]. See Section 8.1 for more discussion of the comparison of my work to Gundry’s.
Matching on partially applied constants Picodoes not contain type families. Instead, it uses λ-abstractions and case expressions, as these are more familiar to functional programmers. And yet, I wish for Pico to support the variety of ways in which type families are used in today’s Haskell. One curiosity of today’s Haskell is that it allows matching on partially applied data constructors:
type familyIsLeft awhere IsLeft ’Left = ’True IsLeft ’Right= ’False
The type family IsLeft is inferred to have kind ∀k.(k →Either k k)→Bool. (Note thatk →Either k k is what you get when unifying the kind ofLeft with that ofRight.) That is, it matches on the Left and Right constructors, even though these are not applied to arguments. While it may seem that IsLeft is matching on a function—after all, the type ofIsLeft’s argument appears to be an arrow type—it is not. It is matching only on constructors, because today’s kind-level→classifies only type constants. That is, it really should be spelled ’→.
To support functions such as IsLeft,Picoallowscasescrutinees to have matchable ’Π-types, instead of just fully applied datatypes. As designed here, matching on partially applied data constructors is also available at the term level inPico. However, practical considerations (e.g., how would you compile such a match?) may lead us to prevent the use of this feature from surface Haskell.
5.1.1.3 Matching on Type
Today’s Haskell also has the ability, through its type families, to match on members of Type. For example:
41For example, we can prove(λx:
RelInt.3) 4∼(λx:RelInt.3) 5but do not wish to be able to prove 4 ∼5.
type familyIntLike x where IntLike Integer = ’True IntLike Int = ’True IntLike = ’False
This ability for a function to inspect the choice of a type—and not a code for a type—is unique among production languages to Haskell, as far as I am aware. With the type families in today’s Haskell, discerning between types is done by simple pattern matching. However, if we compile type families to case statements, we need a way to deal with this construct, even though Type is not an algebraic datatype.
Fortunately, types likeEitherresemble data constructors likeJust: both are classified by matchable quantification(s) over a type headed by another type constant. In the case ofEither, we haveEither :’Π_:RelType,_:RelType.Type;42note that the body of the
Π-type is headed by the constantType. ForJust, we haveJust{a} :’Π_:Rela.Maybea.43
With this similarity, it is not hard to create a typing rule for a casestatement that can handle both data constructors (like Just) and types (like Either).
A key feature, however, that is needed to support matching on Type is default patterns. For a closed datatype, where all the constructors can be enumerated, default patterns are merely a convenience; any default can be expanded to list all possible constructors. For an open type, like Type, the availability of the default pattern is essential. It is for this reason alone that I have chosen to include default patterns in
Pico.
5.1.1.4 Hypothetical equality
Pico allows abstraction over coercions, much like any λ-calculus allows abstraction
over expressions (or, in a call-by-value calculus, values). Coercion abstraction means that a type equality may be assumed in a given type. When we wish to evaluate a term that assumes an equality, we must apply that term to evidence that the equality holds—an actual coercion. It is this ability, to assume an equality, that allows Pico to have GADTs. See the example in Section 5.5 for the details.