1. Introduction
Local type inference(Pierce and Turner, 2000) is a simple yet effective partial technique for inferring types for programs. In contrast to complete methods of type inference such as the DamasMilner system(Damas and Milner, 1982) which can type programs without any type annotations by restricting the language of types, partial methods require the programmer to provide some type annotations and, in exchange, are suitable for use in programming languages with rich type features such as impredicativity and subtyping(Pierce and Turner, 2000; Odersky et al., 2001), dependent types(Xi and Pfenning, 1999), and higherrank types(Peyton Jones et al., 2005), where complete type inference may be undecidable.
Local type inference is also contrasted with global inference methods (usually based on unification) which are able to infer more missing annotations by solving typing constraints generated from the entire program. Though more powerful, global inference methods can also be more difficult for programmers to use when type inference fails, as they can generate type errors whose root cause is distant from the location the error is reported(McAdam, 2002). Local type inference address this issue by only propagating typing information between adjacent nodes of the abstract syntax tree (AST), allowing programmers to reason locally about type errors. It achieves this by using two main techniques: bidirectional type inference rules and local typeargument inference.
The first of these techniques, bidirectional type inference, is not unique to local type inference ((Dunfield and Krishnaswami, 2013; Peyton Jones et al., 2005; Vytiniotis et al., 2006; Xie and Oliveira, 2018) are just a few examples), and uses two main judgment forms, often called synthesis and checking mode. When a term synthesizes type , we view this typing information as coming up and out of and as available for use in typing nearby terms; when checks against type (called in this paper the contextual type), this information is being pushed down and in to and is provided by nearby terms.
The second of these techniques, local typeargument inference, finds the missing types arguments in polymorphic function applications by using only the type information available at an application node of the AST. For a simple example, consider the expression where id has type and has type ℕ. Here we can perform synthetic typeargument inference by synthesizing the type of and comparing this to the type of pair to infer that the missing type argument instantiating is ℕ.
Using these two techniques, local type inference has a number of desirable properties. Though some annotations are still required, in practice a good number of type annotations can be omitted, and often those that need to remain are predictable and coincide with programmers’ expectations that they serve as useful and machinechecked documentation(Pierce and Turner, 2000; Hosoya and Pierce, 1999). Without further instrumentation, local type inference already tends to report type errors close to where further annotations are required; more recently, it has been used in (Plociniczak, 2016) as the basis for developing autonomous typedriven debugging and error explanations. The type inference algorithms of (Pierce and Turner, 2000; Odersky et al., 2001) admit a specification for their behavior, helping programmers understand why certain types were inferred without requiring they know every detail of the typechecker’s implementation. Add to this its relative simplicity and robustness when extended to richer type systems and it seems unsurprising that it has been a popular choice for type inference in programming languages.
Unfortunately, local type inference can fail even when it seems like there should be enough typing information available locally. Consider trying to check that the expression has type , assuming pair has type ). The inference systems presented in (Odersky et al., 2001; Pierce and Turner, 2000) will fail here because the argument
does not synthesize a type. The techniques proposed in the literature of local type inference for dealing with cases similar to this include classifying and avoiding such “hardtosynthesize” terms
(Hosoya and Pierce, 1999) and utilizing the partial type information provided by polymorphic functions(Odersky et al., 2001); the former was dismissed as unsatisfactory by the same authors that introduced it and the latter is of no help in this situation, since the type of pair tells us nothing about the expected type of . What we need in this case is contextual typeargument inference, utilizing the information available from the expected type of the whole application to know argument is expected to have type .Additionally, languages using local type inference usually use fullyuncurried applications in order to maximize the notion of “locality” for typeargument inference, improving its effectiveness. The programmer can still use curried applications if desired, but “they are secondclass in this respect.”(Pierce and Turner, 2000). It is also usual for type arguments to be given in an “all or nothing” fashion in such languages, meaning that even if only one cannot be inferred, all must be provided. We believe that currying and partial type applications are useful idioms for functional programming and wish to preserve them as firstclass language features.
1.1. Contributions
In this paper, we explore the design space of local type inference in the setting of System F(Girard, 1986; Girard et al., 1989) by developing spinelocal type inference, an approach that both expands the locality of typeargument inference to an application spine and augments its effectiveness by using the contextual type of the spine. In doing so, we

show that we can restore firstclass currying, partial type applications, and infer the types for some “hardtosynthesize” terms not possible in other variants of local type inference;

provide a specification for contextual typeargument inference with respect to which we show our algorithm is sound and complete

give a weak completeness theorem for our type system with respect to fully annotated System F programs, indicating the conditions under which the programmer can expect type inference succeeds and where additional annotations are required when it fails.
Spinelocal type inference is being implemented in Cedille(Stump, 2017), a functional programming language with higherorder and impredicative polymorphism and dependent types and intersections. Though the setting for this paper is much simpler, we are optimistic that spinelocal type inference will serve as a good foundation for type inference in Cedille that makes using its rich type features more convenient for programmers.
The rest of this paper is organized as follows: in Section 2 we cover the syntax and some useful terminology for our setting; in Section 3 we present the type inference rules constituting a specification for contextual typeargument inference, consider its annotation requirements, and illustrate its use, limitations, and the type errors it presents to users; in Section 4 we show the prototypematching algorithm implementing contextual typeargument inference; and in Section 5 we discuss how this work compares to other approaches to type inference.
2. Internal and External Language
Type inference can be viewed as a relation between an internal language of terms, where all needed typing information is present, and an external language, in which programmers work directly and where some of this information can be omitted for their convenience. Under this view, type inference for the external language not only associates a term with some type but also with some elaborated term in the internal language in which all missing type information has been restored. In this section, we present the syntax for our internal and external languages as well as introduce some terminology that will be used throughout the rest of this paper.
2.1. Syntax
We take as our internal language explicitly typed System F (see (Girard et al., 1989)); we review its syntax below:
Types  
Contexts  
Terms 
Types consist of type variables, arrow types, and type quantification, and typing contexts consist of the empty context, type variables (also called the context’s declared type variables), and term variables associated with their types. The internal language of terms consists of variables, λabstractions with annotations on bound variables, Λabstractions for polymorphic terms, and term and type applications. Our notational convention in this paper is that term metavariable indicates an elaborated term for which all type arguments are known, and indicates a partially elaborated term where some type arguments are type metavariables (discussed in Section 3).
The external language contains the same terms as the internal language as well as bare λabstractions – that is, λabstractions missing an annotation on their bound variable:
Terms 
Types and contexts are the same as for the internal language and are omitted.
2.2. Terminology
In both the internal and external languages, we say that the applicand of a term or type application is the term in the function position. A head a is either a variable or λabstraction (bare or annotated), and an application spine(Cervesato and Pfenning, 2003) (or just spine) is a view of an application as consisting of some head (called the spine head) followed by a sequence of (term and type) arguments. The maximal application of a subexpression is the spine in which it occurs as an applicand, or just the subexpression itself if it does not. For example, spine is the maximal application of itself and its applicand subexpressions , , and , with as head of the spine. Predicate indicates term is some term or type application (in either language) and we define it formally as .
Turning to definitions for types and contexts, function calculates the set of declared type variables of context and is defined recursively by the following set of equations:
Predicate indicates that type is wellformed under – that is, all free type variables of occur as declared type variables in (formally ).
3. Type Inference Specification



The typing rules for our internal language are standard for explicitly typed System F and are omitted (see Ch. 23 of (Pierce, 2002) for a thorough discussion of these rules). We write to indicate that under context internal term has type . For type inference in the external language, Figure 1 shows judgment which consists mostly of standard (except for and ) bidirectional inference rules with elaboration to the internal language, and Figure 2 shows the specification for contextual typeargument inference. Judgment in Figure 1(b) handles traversing the spine and judgment in Figure 1(c) types its term applications and performs typeargument inference (both synthetic and contextual). Figure 1(a) gives a “shim” judgment which bridges the bidirectional rules with the specification for rhetorical purposes (discussed below). Though these rules are not algorithmic, they are syntaxdirected, meaning that for each judgment the shape of the term we are typing (i.e. the subject of typing) uniquely determines the rules that applies.
Bidirectional Rules
We now consider more closely each judgment form and its rules starting with , the point of entry for type inference. The two modes for type inference, checking and synthesizing, are indicated resp. by (suggesting pushing a type down and into a term) and (suggesting pulling a type up and out of a term). Following the notational convention of Peyton Jones et al.(Peyton Jones et al., 2005) we abbreviate two inference rules that differ only in their direction to one by writing , where is a parameter ranging over . We read judgment as: “under context , term synthesizes type and elaborates to ,” and a similar reading for checking mode applies for . When the direction does not matter, we will simply say that we can infer has type .
Rule is standard. Rule says we can infer missing type annotation on a λabstraction when we have a contextual arrow type . Rules and say that Λ and annotated λabstractions can have their types either checked or synthesized. says that a type application has its type inferred in either mode when the applicand synthesizes a quantified type. The reason for this asymmetry between the modes of the conclusion and the premise is that even when in checking mode, it is not clear how to work backwards from type to .
and are invoked on maximal applications and are the first nonstandard rules. To understand how these rules work, we must 1) explain the “shim” judgment serving as the interface for spinelocal typeargument inference and 2) define metalanguage function . Read as: “under context and with (optional) contextual type , partially infer application has type with elaboration and solution ,” where is a substitution mapping a some metavariables (i.e. omitted type arguments) in to contextuallyinferred type arguments.
In rule , is provided to indicating no contextual type is available. We constrain to be the identity substitution (written ) and that elaborated term has no unsolved metavariables, matching our intuition that all type arguments must be inferred synthetically. In rule , we provide the contextual type to and check (implicitly) that it equals and (explicitly) that all remaining metavariables in are solved by , then elaborate (the replacement of each metavariable in with its entry in ). Shared by both is the second premise of the (anonymous) rule introducing that solves precisely the metavariables of the partially inferred type for application .
Metavariables
What are the “metavariables” of elaborations and types? When is a term application with some type arguments omitted in its spine, its partial elaboration from spinelocal typeargument inference under context fills in each missing type argument with either a wellformed type or with a metavariable (a type variable not declared in ) depending on whether it was inferred synthetically. For example, if and we wanted to check that it has type under a typing context associating pair with type and with type ℕ, then we could derive
(assuming some base type ℕ, some family of base types for all types and , and assuming is not declared in .) Looking at the partial elaboration of , we would see that type argument was inferred from its contextual type and that was inferred from the synthesized type of the arguments to pair.
Metavariables never occur in a judgment formed by , only in the judgments of Figure 2. In particular, these rules enforce that metavariables in a partial elaboration can occur only as type arguments in its spine, not within its head or term arguments. This restriction guarantees spinelocal typeargument inference and helps to narrow the programmer’s focus when debugging type errors. Furthermore, metavariables correspond to omitted type arguments injectively, significantly simplifying the kind of reasoning needed for debugging type errors. We make this precise by defining metalanguage function which yields the set of metavariables occurring in its second argument with respect to the context . is overloaded to take both types and elaborated terms for its second argument: for types we define , the set of free variables in less the declared type variables of ; for terms, is defined recursively by the following equations:
Using our running example where the subject is we can now show how the metavariable checks are used in rules and . We have for our partially elaborated term that and also for our type that . If we have a derivation of the judgment above formed by we can then derive with rule
because substitution solves the remaining metavariable in the elaborated term and type, and when utilized on the partially inferred type yields the contextual type for the term. However, we would not be able to derive with rule
since we do not have as our solution and we have metavariable remaining in our partial elaboration and type. Together, the checks in and ensure that metavariables are never passed up and out of a maximal application during type inference.
Specification Rules
Judgment serves as an interface to spinelocal typeargument inference. In Figure 1(a) it is defined in terms of the specification for contextual typeargument inference given by judgments and ; we call it a “shim” judgment because in Figure 3(a) we give for it an alternative definition using the algorithmic rules in which the condition is not needed. Its purpose, then, is to cleanly delineate what we consider specification and implementation for our inference system.
Though the details of maintaining spinelocality and performing synthetic typeargument inference permeate the inference rules for and , these rules form a specification in that they fully abstract away the details of contextual typeargument inference, describing how solutions are used but omitting how they are generated. Spinelocality in particular contributes to our specification’s perceived complexity – what would be one or two rules in a fullyuncurried language with allornothing type argument applications is broken down in our system in to multiple inference rules to support currying and partial type applications.
Judgment contains three rules and serves to dig through a spine until it reaches its head, then work back up the spine typing its term and type applications. The reading for it is the same as for , less the optional contextual type. Rule types the spine head by deferring to ; our partial solution is since no metavariables are present in a judgment formed by . is similar to except it additionally propagates solution . Rule is used for term applications: first it partially synthesizes a type for the applicand and then it uses judgment to ensure that the elaborated term with this type can be applied to argument .
Judgment performs synthetic and contextual typeargument inference and ensures that term applications with omitted type arguments are welltyped. We read as “under context , elaborated applicand of partial type together with solution can be applied to term ; the application has type and elaborates with solution .”
Contextual typeargument inference happens in rule , which says that when the applicand has type we can choose to guess any wellformed for our contextual type argument by picking (indicating contains all the mappings present in and an additional mapping for ), or choose to attempt to synthesize it later from an argument by picking . The details of which to guess, or whether we should guess at all, are not present in this specificational rule. In both cases, we elaborate the applicand to of type and check that it can be applied to – we do this even when we guess for to maintain the invariant that for all elaborations and solutions generated from the rules in Figures 1(b) and 1(c) we have , which we need when checking in the (specificational) rule for that these guessed solutions are ultimately justified by the contextual type (if any) of our maximal application.
We illustrate the use of with an example: if the input presented to judgment is
then after two uses of rule where we guess for and decline to guess for we would generate:
After working through omitted type arguments, requires that we eventually reveal some arrow type to type a term application. When it does we have two cases, handled resp. by and : either the domain type of applicand together with solution provide enough information to fully know the expected type for argument (i.e. ), or else they do not and we have some nonempty set of unsolved metavariables in corresponding to type arguments we must synthesize. Having full knowledge, in we check has type ; otherwise, in we try to solve metavariables by synthesizing a type for and checking it is instantiation
(vectorized notation for the simultaneous substitution of types
for ) of . Once done, we conclude with result type and elaboration for the application, as the metavariables of corresponding to omitted type arguments have now been fully solved by typeargument synthesis. Together, and prevent metavariables from being passed down to term argument , as we require that it either check against or synthesize a wellformed type.We illustrate the use of rule with and example: suppose that under context the input presented to judgment is
and furthermore that . Then we have instantiation from synthetic typeargument inference and use it to produce for the application the result type and the elaboration . Note that synthesized type arguments are used eagerly, meaning that the typing information synthesized from earlier arguments can in some cases be used to infer the types of later arguments in checking mode (see Section 3.2). This is reminiscent of greedy typeargument inference for type systems with subtyping(Cardelli, 1997; Dunfield, 2009), which is known to cause unintuitive type inference failures due to suboptimal type arguments (i.e. less general wrt to the subtyping relation) being inferred. As System F lacks subtyping, this problem does not affect our type inference system and we can happily utilize synthesized type arguments eagerly (see Section 5).
3.1. Soundness, Weak Completeness, and Annotation Requirements
The inference rules in Figure 2 for our external language are sound with respect to the typing rules for our internal language (i.e. explicitly typed System F), meaning that elaborations of typeable external terms are typeable at the same type^{1}^{1}1A complete list of proofs for this paper can be found in the proof appendix at TODO:
Theorem 3.1 ().
(Soundness of ):
If then .
Our inference rules also enjoy a trivial form of completeness that serves as a sanitycheck with respect to the internal language: since any term in the internal language (i.e., any fully annotated term) is also in the external language, we expect that should be typable using the typing rules for external terms:
Theorem 3.2 ().
(Trivial Completeness of ):
If then
A more interesting form of completeness comes from asking which external terms can be typed – after all, this is precisely what a programmer needs to know when trying to debug a type inference failure! Since our external language contains terms without any annotations and our type language is impredicative System F, we know from (Wells, 1998) that type inference is in general undecidable. Therefore, to state a completeness theorem for type inference we must first place some restrictions on the set of external terms that can be the subject of typing.
We start by defining what it means for to be a partial erasure of internal term . The grammar given in Section 2 for the external language does not fully express where we hope our inference rules will restore missing type information. Specifically, the rules in Figures 1 and 2 will try to infer annotations on bare λabstractions and only try to infer missing type arguments that occur in the applicand of a term application. For example, given (welltyped) internal term and external term , our inference rules will try to infer the missing type arguments and but will not try to infer the missing .
A more artificial restriction on partial erasures is that the sequence of type arguments occurring between two terms in an application can only be erased in a righttoleft fashion. For example, given internal term , the external term is a valid erasure ( and are erased between and , and between and rightmost is erased), but term is not. This restriction helps preserve soundness of the external type inference rules by ensuring that every explicit type argument preserved in an erasure of an internal term instantiates the same type variable it did in ; it is artificial because we could instead have introduced notation for “explicitly erased” type arguments in the external language, such as , to indicate the first type argument has been erased, but did not to simplify the presentation of our inference rules and language.
The above restrictions for partial erasure are made precise by the functions and which map an internal term to sets of partial erasures . They are defined mutually recursively below:
We are now ready to state a weak completeness theorem for typing terms in the external language which overapproximates the annotations required for type inference to succeed (we write to mean some number of type quantifications over type )
Theorem 3.3 ().
(Weak completeness of ):
Let be a term of the internal language and be a term of the internal languages such that . If then when the following conditions hold for each subexpression of , corresponding subexpression of , and corresponding subderivation of :

If for some and , then for some

If occurs as a maximal term application in and if
for some and , then . 
If is a term application and for some and , and if for some and , then for some and .

If is a type application and for some and , and for some and , then for some .
Theorem 3.3 only considers synthetic typeargument inference, and in practice condition (1) is too conservative thanks to contextual typeargument inference. Though a little heavyweight, our weak completeness theorem can be translated into a reasonable guide for where type annotations are required when type synthesis fails. Conditions (3) and (4) suggest that when the applicand of a term or type application already partially synthesizes some type, the programmer should give enough type arguments to at least reveal it has the appropriate shape (resp. a type arrow or quantification). (2) indicates that type variables that do not occur somewhere corresponding to a term argument of an application should be instantiated explicitly, as there is no way for synthetic typeargument inference to do so. For example, in the expression if has type there is no way to instantiate from synthesizing argument . Finally, condition (1) we suggest as the programmer’s last resort: if the above advice does not help it is because some λabstractions need annotations.
Note that in conditions (2), (3), and (4) we are not circularly assuming type synthesis for subexpressions of partial erasure succeeds in order to show that it succeeds for , only that if a certain subexpression can be typed then we can make some assumptions about the shape of its type or elaboration. Conditions (3) and (4) in particular are a direct consequence of a design choice we made for our algorithm to maintain injectivity of metavariables to omitted type arguments. As an alternative, we could instead refine metavariables when we know something about the shape of their instantiation. For example, if we encountered a term application whose applicand has a metavariable type , we know it must have some arrow type and could refine to , where and are fresh metavariables. However, doing so means type errors may now require nontrivial reasoning from users to determine why some metavariables were introduced in the first place.
Still, we find it somewhat inelegant that our characterization of annotation requirements for type inference is not fully independent of the inference system itself. For programmers using these guidelines, this implies that there must be some way to interactively query the typechecker for different subexpressions of a program during debugging. Fortunately, many programming languages offer just such a feature in the form of a REPL, meaning that in practice this is not too onerous a requirement to make.
Theorem 3.3 only states when an external term will synthesize its type, but what about when a term can be checked against a type? It is clear from the typing rules in Figure 1 that some terms that fail to synthesize a type may still be successfully checked against a type. Besides typing bare λabstractions (which can only have their type checked), checking mode can also reduce the annotation burden implied by condition (2) of Theorem 3.3: consider again the example where has type . If instead of attempting type synthesis we were to check that it has some type then we would not need to provide an explicit type argument to instantiate .
From these observations and our next result, we have that checking mode of our type inference system can infer the types of strictly more terms than can synthesizing mode – whenever a term synthesizes a type, it can be checked against the same type.
Theorem 3.4 ().
(Checking extends synthesizing):
If then
3.2. Examples



Successful Type Inference
We conclude this section with some example programs for which the type inference system in Figures 1 and 2 will and will not be able to type. We start with the motivating example from the introduction of checking that the expression has type , which is not possible in other variants of local type inference. For convenience, we assume the existence of a base type ℕ and a family of base types for all types and . These assumptions are admissible as we could define these types using Church encodings. A full derivation for typing this program is given in Figure 3, including the following abbreviations:
To type this application we first dig through the spine, reach the head pair, and synthesize type . No metavariables are generated by judgment and thus there can be no metavariable solutions, so we generate solution .
Next we type the first application, , shown in subderivation . In the first invocation of rule we guess solution for , and in the second invocation we decline to guess an instantiation for (in this example we could have also guessed for as this information is also available from the contextual type, but choose not to in order to demonstrate the use of all three rules of ). Then using rule we check argument against . This is the point at which the local type inference systems of (Pierce and Turner, 2000; Odersky et al., 2001) will fail: as a bare λabstraction this argument will not synthesize a type, and the expected type as provided by the applicand pair alone does not tell us what the missing type annotation should be. However, by using the information provided by the contextual type of the entire application we know it must have type . The resulting partial type of the application is , and we propagate solution to the rest of the derivation. Note that we elaborate the argument of this application to – we never pass down metavariables to term arguments, keeping typeargument inference local to the spine.
In subderivation we type (parentheses added) where our applicand has partial type . We find that we have unsolved metavariable as the expected type for , so we use rule and synthesize the type for . Using solution , we produce for the resulting type of the application and elaborate the application to a , wherein type argument is replaced by ℕ in the original elaborated applicand .
Finally, in rule we confirm that the only metavariables remaining in our partial type synthesis of the application is precisely those for which we knew the solutions from the contextual type. For this example, the only remaining metavariable in both the partially synthesized type and elaboration is , which is also the only mapping in , so type inference succeeds. We use to replace all occurrences of with in the type and elaboration and conclude that term can be checked against type .
The next example illustrates how our eager use of synthetic typeargument inference can type some terms not possible in other variants of local type inference. Consider checking that the expression has type ℕ, where rapp has type and has type ℕ. From the contextual type we know that should be instantiated to ℕ, and when we reach application , we learn that should be instantiated to ℕ from the synthesized type of . Together, this gives us enough information to know that argument should have type . Such eager instantiation is neither novel nor necessarily desirable when extended to richer types or more powerful systems of inference (see Section 5), but in our setting it is a useful optimization that we happily make for inferring the types of expressions like the one above.
Type Inference Failures
To see where type inference can fail, we again use but now ask that it synthesize its type. Rule insists that we make no guesses for metavariables (as there is no contextual type for the application that they could have come from), so we would need to synthesize a type for argument – but our rules do not permit this! In this case the user can expect an error message like the following:
expected type: ?X error: We are not in checking mode, so bound variable x must be annotated
where ?X
indicates an unsolved metavariable
corresponding to type variable in the type of pair. The
situation above corresponds to condition (1) of Theorem 3.3:
in general, if there is not enough information from the type of an
applicand and the contextual type of the application spine in which it
occurs to fully know the expected types of arguments that are
λabstractions, then such arguments require explicit type annotations.
We next look at an example corresponding to condition (2) of Theorem 3.3, namely that the type variables of a polymorphic function that do not correspond to term arguments in an application should be instantiated explicitly. Here we will assume a family of base types for every type and , a variable right of type , and a variable of type . In trying to synthesize a type for the application the user can expect an error message like:
synthesized type: (?X + ) error: This maximal application has unsolved metavariables
indicating that type variable requires an explicit type argument be provided. Fortunately for the programmer, and unlike the local type inference systems of (Pierce and Turner, 2000; Odersky et al., 2001), our system supports partial explicit type application, meaning that can be instantiated without also explicitly (and redundantly) instantiating . On the other hand, local type inference systems for System F(Pierce and Turner, 2000; Odersky et al., 2001) can succeed to type without additional type arguments, as they can instantiate to the minimal type (with respect to their subtyping relation) Bot. Partial type application, then, is more useful for our setting of System F where picking some instantiation for this situation would be somewhat arbitrary.
A more subtle point of failure for our algorithm corresponds to conditions (3) and (4) of Theorem 3.3. Even when the head and all arguments of an application spine can synthesize their types, the programmer may still be require to provide some additional type arguments. Consider the expression , where and . Even with some contextual type for this expression, type inference still fails because the rules in Figure 1(c) require that the type of the applicand of a term application reveals some arrow, which does not. The programmer would be met with the following error message:
applicand type: ?X error: The type of an applicand in a term application must reveal an arrow
prompting the user to provide an explicit type argument for . To make expression typeable, the programmer could write , or even – our inference rules are able to solve metavariables introduced by explicit and even synthetic type arguments, as long as there is at least enough information to reveal a quantifier or arrow in the type of a term or type applicand.
For our last type error example, we consider the situation where the programmer has written an illtyped program. Local type inference enjoys the property that type errors can be understood locally, without any “spooky action” from a distant part of the program. In particular, with local type inference we would like to avoid error messages like the following:
synthesized type: expected type: ?X := error: type mismatch
From this error message alone the programmer has no indication of why the expected type is ! In our type inference system we expand the distance information travels by allowing it to flow from the contextual type of an application to its arguments. As an example, the error message above might be generated when checking that the expression has type , specifically when inferring the type of the first argument. Fortunately, our notion of locality is still quite small and we can easily demystify the reason type inference expected a different type:
synthesized type: expected type: ?X := contextual match: ?X ?Y := ( )
where contextual match tells the programmer to compare to the partially synthesized and contextual return types of the application to determine why was instantiated to . A similar field, synthetic match, could tell the programmer that the type of an earlier argument informs the expected type of current one.
4. Algorithmic Inference Rules




The type inference system presented in Section 3 do not constitute an algorithm. Though the rules forming judgment indicate where and how we use contextuallyinferred type arguments, they do not specify what their instantiations are or even whether this information is available to use, and it is not obvious how to work backwards from the second premise in Figure 1(a) to develop an algorithm.
Figure 4 shows the algorithmic rules implementing contextual typeargument inference. The full algorithm for spinelocal type inference, then, consists of the rules in Figure 1 with the shim judgment as defined in Figure 3(a). At the heart of our implementation is our prototype matching algorithm; to understand the details of how we implement contextual typeargument inference, we must first discuss this algorithm and the two new syntactic categories it introduces, prototypes and decorated types.
4.1. Prototype Matching
Figure 3(d) lists the rules for the prototype matching algorithm. We read the judgment as: “solving for metavariables , we match type to prototype and generate solution and decorated type ,” and we maintain the invariant that . Metavariables can only occur in , thus these are matching (not unification) rules. The grammar for prototypes and decorated types is given below:
Prototypes  
Decorated Types  
Prototypes carry the contextual type of the maximal application of a
spine. In the base case they are either the uninformative (as in
), indicating no contextual type, or they are informative of
type (as in ). In this way, prototypes generalize the
syntactic category we introduced earlier for optional contextual
types. We use the last prototype former as we work our way
down an application spine to track the expected arity of its head. For
example, if we wished to check that the expression id suc x
has
type , then when we reached the head id
using the
rules in Figure 3(b) we would generate for it prototype
Decorated types consist of types (also called plaindecorated types), an arrow with a regular type as the domain (as prototypes only inform us of the result type of a maximal application, not of the types of arguments), quantified types whose bound variable may be decorated with the type to which we expect to instantiate it, and “stuck” decorations. On quantifiers, decoration indicates that did not inform us of an instantiation for – we sometimes abbreviate the two cases as , where and .
To explain the role of stuck decorations, consider again id suc x
.
Assuming id
has type , matching
this with prototype generates decorated type
, meaning that we only know
that will be instantiated to some type that matches . Stuck decorations occur when the expected arity of a
spine head (as tracked by a given prototype) is greater than the arity
of the type of the head and are the mechanism by which we propagate a
contextual type to a head that is “overapplied” – a notuncommon
occurrence in languages with curried applications!
Turning to the prototype matching algorithm in Figure 3(d), rule says that we match an arrow type and prototype when we can match their codomains. Rule says that when the prototype is some type we must find an instantiation such that , and rule says that any type matches with with no solutions generated (thus we call the “uninformative” prototype). In rule we match a quantified type with a prototype by adding bound variable to our metavariables and matching the body to the same prototype; the substitution in the conclusion, , is the solution generated from this match less its mapping for , which is placed in the decoration . For example, matching with prototype generates decorated type . Finally, rule applies when there is incomplete information (in the form of ) on how to instantiate a metavariable; we generate a stuck decoration with identity solution .
We conclude by showing that our prototype matching rules really do constitute an algorithm: when , , and are considered as inputs then behaves like a function.
Theorem 4.1 ().
(Functionness of ):
Given , , and , if
and , then and
4.2. Decorated Type Inference
We now discuss the rules in Figures 3(b) and 3(c) which implement contextual typeargument inference (as specified by Figures 1(b) and 1(c)) by using the prototype matching algorithm. We begin by giving a reading for judgments – read as: “under context and with prototype , synthesizes decorated type and elaborates with solution ,” where again represents the contextuallyinferred type arguments.
In rule we required that the solution generated by in its premise is ; in we (implicitly) required that the contextual type is equal to ; and now with the algorithmic definition for we appear to be requiring in both that the decorated type generated by is a plaindecorated type . With the algorithmic rules, these are not requirements but guarantees that the specification makes of the algorithm:
Lemma 4.2 ().
Let be the number of prototype arrows prefixing and be the number of decorated arrows prefixing . If then
Theorem 4.3 ().
(Soundness of wrt ):
If then
Assuming prototype inference succeeds, when we specialize in Theorem 4.3 to we have immediately by rule that ; when we specialize it to some contextual type for an application, then by the premise of we have . Theorem 4.2 and 4.3 together tell us that we generate plaindecorated types in both cases, as in particular we cannot have leading (decorated) arrows or stuck decorations with prototypes or .
Next we discuss the rules forming judgment in Figure 3(b), constituting the algorithmic version of the rules in Figure 1(b). In rule , after synthesizing a type for the application head we match this type against expected prototype (we are guaranteed the prototype has this shape since only a term application can begin a derivation of ). No metavariables occur in initially – as we perform prototype matching these will be generated by rule from quantified type variables in and their solutions will be left as decorations in the resulting decorated type . We are justified in requiring that matching to generates empty solution since we have in general that the metavariables solved by our prototype matching judgment are a subset of the metavariables it was asked to solve:
Lemma 4.4 ().
If then
In , we can infer the type of a type application when synthesizes a decorated type and is either an uninformative decoration or is precisely (that is, the programmer provided explicitly the type argument the algorithm contextually inferred). We synthesize for the type application, where we extend type substitution to decorated types by the following recursive partial function:
This definition is straightforward except for the last case dealing with stuck decorations. Here, (representing instantiations given by explicit or syntheticallyinferred type arguments) may provide information on how to instantiate and this must match our current (though incomplete) information from about our contextuallyinferred type arguments. For example, if we have decorated type , then would require we match with and matching would generate (plain) decorated type
The definition of substitution on decorated types is partial since prototype matching may fail (consider if we used substitution in the above example instead). When a decorated type substitution appears in the conclusion of our algorithmic rules, such as in or , we are implicitly assuming an additional premise that the result is defined.
The last rule for judgment is , and like it benefits from a reading for judgment occurring in its premise. We read as: “under , elaborated applicand of decorated type together with solution can be applied to ; the application has decorated type and elaborates with solution .” Thus, says that to synthesize a decorated type for a term application we synthesize the decorated type of the applicand and ensure that the resulting elaboration , along with its decorated type and solution, can be applied to .
We now turn to the rules for the last judgment of our algorithm. Rule clarifies the nondeterministic guessing done by the corresponding specificational rule : the contextuallyinferred type arguments we build during contextual typeargument inference are just the accumulation of quantified type decorations. The solution we provide to the second premise of contains mapping if is an informative decoration, and as we did in rule we provide elaborated term to track the contextuallyinferred type arguments separately from those synthetically inferred.
Rule works similarly to : when the only metavariables in the domain of our decorated type are solved by , we can check that argument has type . In rule we have some metavariables in not solved by – we synthesize a type for the argument, ensure that it is some instantiation of , and use this instantiation on the metavariables in as well as the decorated codomain type , potentially unlocking some stuck decoration to reveal more arrows or decorated type quantifications.
We conclude this section by noting that the specificational and algorithmic type inference system are equivalent, in the sense that they type precisely the same set of terms:
Theorem 4.5 ().
(Soundness of wrt ):
If then
Theorem 4.6 ().
(Completeness of wrt ):
If then
(where indicates is defined as in Figure 3(a))
Taken together, Theorems 4.5 and 4.6 justify our claim that the rules of Figure 2 constitute a specification for contextual typeargument inference – it is not necessary that the programmer know the notably more complex details of prototype matching or type decoration to understand how contextual type arguments are inferred. Indeed, the judgment provides more flexibility in reasoning about type inference than does , as in rule we may freely decline to guess a contextual type argument even when this would be justified and instead try to learn it synthetically. In contrast, algorithmic rule requires that we use any informative quantifier decoration. We use this flexibility when giving guidelines for the required annotations in Section 3.1 for typing external terms, as the required conditions for typeability in Theorem 3.3 would be further complicated if we could not restrict ourselves to using only synthetic typeargument inference.
5. Discussion & Related Work
5.1. Local Type Inference and System F
Local Type Inference
Our work is most influenced by the seminal paper by Pierce and Turner(Pierce and Turner, 2000) on local type inference that describes its broad approach, including the two techniques of bidirectional typing rules and local typeargument inference and the designspace restriction that polymorphic function applications be fullyuncurried to maximize the benefit of these techniques. In their system, either all term arguments to polymorphic functions must be synthesized or else all type arguments must be given – no compromise is available when only a few type arguments suffice to type an application, be they provided explicitly or inferred contextually. Our primary motivation in this work was addressing these issues – restoring firstclass currying, enabling partial type application, and utilizing the contextual type of an application for typeargument inference – while maintaining some of the desirable properties of local type inference and staying in the spirit of their approach.
Colored Local Type Inference
Odersky, Zenger, and Zenger(Odersky et al., 2001) improve upon the type system of Pierce and Turner by extending it to allow partial type information to be propagated downwards when inferring types for term arguments. Their insight was to internalize the two modes of bidirectional type inference to the structure of types themselves, allowing different parts of a type to be synthetic or contextual. In contrast, we use an “all or nothing” approach to type propagation: when we encountered a term argument for which we have incomplete information, we require that it fully synthesize its type. On the other hand, their system uses only the typing information provided by the application head, whereas we combine this with the contextual type of an application, allowing us to type some expressions their system cannot. The upshot of the difference in these systems is that spinelocal type inference utilizes more contextual information and colored local type inference utilizes contextual information more cleverly.
The syntax for prototypes in our algorithm was directly inspired by the prototypes used in the algorithmic inference rules for (Odersky et al., 2001). Our use of prototypes complements theirs; ours propagates the partial type information provided by contextual type of an application spine to its head, whereas theirs propagates the partial type information provided by an application head to its arguments. In future work, we hope to combine these two notions of prototype to propagate partially the type information coming from the application’s contextual type and head to its arguments.
Subtyping
Local type inference is usually studied in the setting of System F which combines impredicative parametric polymorphism and subtyping. The reason for this is twofold: first, a partial type inference technique is needed as complete type inference for F is undecidable(Tiuryn and Urzyczyn, 1996); second, global type inference systems fail to infer principal types in F (Odersky, 2002; Odersky et al., 1999; Kennedy, 1996), whereas local type inference is able to promise that it infers the “locally best”(Pierce and Turner, 2000) type arguments (i.e. the type arguments minimizing the result type of the application, relative to the subtyping relation). The setting for our algorithm is System F, so the reader may ask whether our developments can be extended gracefully to handle subtyping. We believe the answer is yes, though with some modification on how synthetic type arguments are used.
Comments
There are no comments yet.