• No se han encontrado resultados

Depredadores marinos apicales como especies de estudio

Bake is a bidirectional [78] constraint-generation algorithm [79]. It walks over the

input syntax tree and generates constraints, which are later solved. It can operate in either a synthesis mode (when the expected type of an expression is unknown) or in checking mode (when the type is known). Like prior work [37, 99], I leave the details of the solver unspecified; any solver that obeys the properties described in Section 6.10.1 will do. In practice, the solver will be the one currently implemented in GHC. Despite the fact that the dependency tracking described here is omitted from Vytiniotis et al. [99], the most detailed description of GHC’s solver,75 the solver as implemented does indeed do dependency tracking and should support all of the innovations described in this chapter.

Constraints in Bakeare represented by unification telescopes, which are lists of possibly dependent unification variables,76 with their types. Naturally, there are two sorts of unification variables: types α and coercions ι. The solver finds concrete types to substitute in for unification variables α and concrete coercions to substitute in for unification variables ι. Implication constraints [84, 99] are handled by classifying unification variables by quantified kinds and propositions. See Section 6.3.

The algorithm is stated as several judgments of the following general form:77

Σ; Ψ`inputs outputs aΩ

Most judgments are parameterized by a fixed signature Σ that defines the datatypes that are in scope.78 The context Ψ is a generalization of contexts Γ; a context Ψ

contains both Picovariables and unification variables. Because this is an algorithmic treatment of type inference, the notation is careful to separate inputs from outputs. Everything to the left of is an input; everything to the right is an output. Most judgments also produce an output Ω, which is a unification telescope, containing bindings for only unification variables. This takes the place of the emitted constraints

75In the paper describing

OutsideIn [99], the authors separate out the constraint generation

from the solver. They call the constraint-generation algorithmOutsideInand the solver remains unnamed. I use the monikerOutsideIn to refer both to the constraint-generation algorithm and the solver.

76Depending on the source, various works in the literature refer to unification variables as existential

variables (e.g., [24]) or metavariables (e.g., [37] and the GHC source code). I prefer unification variables here, as I do not wish to introduce confusion with existentials of data constructors nor the metavariables of my metatheory.

77The definitions forΨandappear in Figure 6.2 on page 147.

78I do not consider in this dissertation how these signatures are formed. To my knowledge, there is

no formal presentation of the type-checking of datatype declarations, and I consider formalizing this process and presenting an algorithm to be important future work.

seen in other constraint-generation algorithms. It also serves as a context in which to type-check the remainder of the syntax tree.

The solver’s interface looks like this:

Σ; Ψ `solv Ω ∆; Θ

That is, it takes as inputs the current environment and a unification telescope. It produces outputs of ∆, a telescope of variables to quantify over, and Θ, thezonker

(Section 6.3.1), which is an idempotent substitution from unification variables to other types/coercions. To understand the output ∆, consider checking the declaration

y =λx →x. The variablex gets assigned a unification variable typeα. No constraints then get put on that type. When trying to solve the unification telescope α:IrrelType,

we have nothing to do. The way forward is, of course, to generalize. So we get

∆ = a:IrrelTypeandΘ = a/α. In the constraint-generation rules for declarations, the

body of a declaration and its type are generalized over∆. (SeeIDecl_Synthesize in Section 6.7.)

Writing a type inference algorithm for a dependently typed language presents a challenge in that the type of an expression can be very intricate. Yet we still wish to infer types for unannotated expressions. To resolve this tension, Bake adheres to the following:

Guiding Principle. In the absence of other information, infer a simple type. Guiding Principle. Never make a guess.

For example, consider inferring a type for

compose f g =λx →f (g x)

The function compose could naively be given either of the following types:

compose :: (b →c)→(a →b)→(a →c) compose :: ∀ (a::Type) (b::a →Type) (c::∀(x ::a)→b x →Type) . Π (f ::∀(x ::a).Π (y ::b x)→c x y) (g :: Π (x::a)→b x) (x ::a) →c x (g x)

However, we surely want inference to produce the first one. If inference did not tend toward simple types, there would be no hope of retaining principal types in the system. I do not prove that Bake infers principal types, as doing so is meaningless without some non-deterministic specification of the type system, which is beyond the scope of this work. However, I wish to design Dependent Haskell with an eye toward

establishing a principal types result in the future. Inferring only rank-1 types still allows for higher-rank types in a bidirectional type system [74]. Accordingly, it is my hope that inferring only simple types will allow for Dependent Haskell to retain principal types.

The second guiding principle is that Bake should never make guesses. Guesses, after all, are sometimes wrong. By “guess” here, I mean that the algorithm and solver should never set the value of a unification variable unless doing so is the only possible way an expression can be well typed. Up until this point, GHC’s type inference algorithm has resolutely refused to guess. This decision manifests itself, among other places, in GHC’s inability to work with a functionf ::F a→F a, where F is a type function.79 The problem is that, fromf 3, there is no way to figure out what a should be, and GHC will not guess the answer.

A key consequence of not making any guesses is that Bake (more accurately, the solver it calls) does no higher-order unification. Consider this example:

fun::a →(f $a)

-- NB: The use of $ means that f is not a matchable function

bad ::Bool →Bool bad x =fun x

In the body of bad, it is fairly clear that we should unify f with the identity function. Yet the solver flatly refuses, because doing so amounts to a guess, given that there are many ways to write the identity function.80

In my choice to avoid higher-order unification, my design diverges from the designs of other dependently typed languages, where higher-order unification is common. Time will tell whether the predictability gotten from avoiding guesses is worth the potential annoyance of lacking higher-order unification. Avoiding guesses is also critical for principal types. See Vytiniotis et al. [99, Section 3.6.2] for some discussion.

Now that we’ve seen the overview, let’s get down to details.

Documento similar