• No results found

Debugging foldRight application

In document Decrypting Local Type Inference (Page 50-55)

2.2 Type inference in Scala

3.1.2 Debugging foldRight application

The introduction has provided an example of a confusing error for an application involving thefoldRightfunction (Figure 1.1). By encoding the example in the core language, and ex- plaining the decisions of the type inference process, we show how limitations of local type in- ference leak to type error messages without offering any obvious type debugging techniques or workarounds to correct it.

We define the problematic snippet using the straightforward encoding of lists (summarized for reference in Appendix A). We assume that the type of thefoldRightterm has been in- ferred as∀a. LIST[a]→ (∀b. LIST[b]→ ((a,b) → b) → b), where Li st denotes a type con- structor of list collection, and a1constant is of a base type Int, where I nt<: . Therefore the erroneousfoldRightapplication can be encoded as:

foldRight(Cons(1,Nil())(Nil())((x, y)→Cons(x+1, y))

An application of the Colored Local Type Inference rules (defined in Figure 2.4) to this appli- cation leads to a type derivation tree shown in Figure 3.4.

The type mismatch is marked in the derivation tree in Figure 3.4 as (type mismatch) and results from the structural inequality between the assigned type of the body of the function, LIST[I nt], and an expected type, LIST[]1.

Debugging typing decisions through a simple visual assessment of the type derivation tree was still a viable option for the function application example from Section 3.1.1, but it is no

1In Scala, type⊥ is equivalent to typeNothingbut has no Java correspondence. The top type is equivalent to typesAnyandObjectin Scala and Java, respectively.

3.1. Using type derivation trees for type debugging (var) ?,  wCons: (app) 1 (type-mismatch) (abs) 3 ...,  wf 5 (var) 6 7 (app) 8 9 (app) w 10 4 ...,  wf (app) w

foldRight(xs)(Nil()): {bList[]}((Int,b)b)b?

Typechecking application of

Typechecking function argumentfun(x,y) Cons(x + 1,y)

?,  foldRight(xs)(Nil())(f): ?,  wfoldRight(xs): ?,  ((Int,b)b)b <:   ?  C6 List[]<: b  C5 foldRight(xs)(Nil())(f) wNil(): {a}List[a]? [?/b]b,  wNil: a.()List[a]? ?,  List[a] <:   ?  C4

wfun(x,y) Cons(x + 1,y): (Int, List[])List[], 

2

w

List[], , x: Int, y: List[] Cons(x+1, y): {aInt}List[a]  List[] 1 = , x: Int, y: List[]

a.((a, List[a]) List[a]) (app) [?/a]a, 1 w(x+1): Int  ? w

(var) [?/a]List[a], 1 List[]  List[?] List[] <:   List[a]  C3 Int <: a  C1 List[] <: List[a]  C2

b.(b((Int,b)b)b)

Figure 3.4: Fragment of a type derivation tree for the applicationfoldRight(xs)(Nil())( f ), where xs=Cons(1,Nil()) and f =fun(x,y)Cons(x + 1,y). Superscripts identify inter- mediate type decisions that led to a type mismatch.

longer the case for thefoldRightapplication. Nevertheless, the systematic type propaga- tion, primarily used to elide more type annotations, also offers a means to backtrack through the type checking process as long as we are able to meticulously follow the decisions of the inference rules.

For example, in Figure 3.4 we notice that the conflicting expected type is being consistently propagated on the failed path in locations 1, 2 and 3. Furthermore, by looking only at the position of the conflicting element in the propagated type information, we see that it is first introduced in the type derivation tree during type variable substitution (superscript 4). The substituted type variable b is first introduced in the inferred type of the partially ap- plied functionfoldRight(xs). Since b is an abstract type variable, there is no need to fur- ther continue the analysis of LIST[]in the left branch of the type derivation tree, (? ,Γ w foldRight(xs) :∀b. b → (Int, b )→ b → b); we have found the location where the conflicting expected type LIST[]is first introduced. The informal description relied only on the infor- mation on how elements of types flow from one type inference rule to the other, irrespective of actual instances of types.

A visually easier to interpret Figure 3.5 presents a stripped down version of the previous derivation tree, which only shows how elements of the inferred type of the partially applied functionfoldRight(xs)(Nil()) are systematically propagated to infer the type of the anony- mous function. It becomes apparent that the root of the tree becomes essentially a point where we shift from backtracking on the type derivation tree to actively using the collected type information to navigate to a different part of the type derivation tree. We call this point a Propagation Root.

(var) ?, wCons: (app) 1 (type-mismatch) (abs) 3 ..., wf 5 (var) 6 7 (app) 8 9 (app) w 10 4 ...,  wf (app) w foldRight(xs)(Nil()): {b List[]} ((Int,b)b)b? Typechecking application of

Typechecking function argument

?,  foldRight(xs)(Nil())(f): ?,  wfoldRight(xs): ?,  ((Int,b)b)b <:  ?  C6 List[]<: b  C5 foldRight(xs)(Nil())(f) wNil(): {a} List[a]? [?/b]b,  wNil: a.()List[a]? ?,  List[a] <:  ?  C4 wfun(x,y)  Cons(x + 1,y):

(Int, List[])List[], 

2

w

List[], , x: Int, y: List[] Cons(x+1, y):

{aInt}List[a]

 List[]

1 = , x: Int, y: List[]

a.((a, List[a]) List[a]) (app) [?/a]a, w

1 (x+1): Int  ?

w

(var) [?/a]List[a], 1 List[]  List[?] List[

] <:  List[a]  C3

Int <: a  C1

List[] <: List[a]  C2

b.(b((Int,b)b)b)

fun(x,y) Cons(x + 1,y)

F e. As !" #e $%a e &to%t 'et ( !e$e  )at  o&%o F e.*w'e et'ea (e$-

o te"e e &ts$o& otex!"a &t 'es o ceo%t'eex!ect e$t ( !eLIST[ ?].

+ts !o ta &tto& ote,t 'att 'ePr/012 145 /6R//4s"7e "(tobe$%%ee&t%o t'eact  a "r //4

o%t'et( !e$e  )at  o&te es &cet'e!ob"e a tc%a e &tw ""t( !ca""(be!a  to%ab e 

t( !e$e  )at  o&t e e&t 'eco !"e t e!oa .

Be% oeweco&t & eo &% o a "t ( !e$e  )a t o&t eeex!"oa to&,&s ea c'% ot 'es o ceo%

t 'e

LIST [?]

t ( !e,we#s t% o a""($e#&et'eco&ce!to%t'e

Pr /0 12 14 5/6 R//4

&o$e to

bet te  & $e s ta & $t ss &  #ca&ce.

8  g 9  f  : ;  

De#&t  o&c'a act e <est'e0 1r= 64o%t 'et( !e&%e e &cej $ e &t, se$"a te &t'ec'a!te 

a& $&t 'e$e#&t  o&o%t'ePr/012 145 /6R//4.

> @  3

P1r=64

/E4H=4J0=5 6E=r=6K=MNO2Q= 64U

V 'e01r=64o%t 'e&%e e &cej $ e &tWP, ¡` X

Y Z\&at( !e$e )a to& te est'e

j $ e&tw'c''asWP,¡` X

YZ\"s t e $aso& eo%t s!e s es.

V 'eWWP,¡` X

YZ\^\& ota t o&$e #&esa% &ct  o&t'ate t  &st'e!ae &to%t'eWP,¡` X

Y Z\&%e e &cej $ e &t . V 'ee s  "to%t 'e% &cto&s&$e #&e$% ot 'er//4o%t'e

t( !e$e  )at  o&te e.

3.1. Using type derivation trees for type debugging

Theorem 1 Propagation of prototype information and Propagation Roots.

We consider any type inference judgment of a form (P, Γ wE : T ), where T might or might not have been inferred, and P = ?, and let the parent of (P, Γ wE : T ) be repre- sented as (PppwEp: Tp).

The prototype P in (P, Γ wE : T ) has been either propagated from its parent, i.e., P is part of the prototype Pp, or it has been introduced for the first time in its parent and P is not part of Pp. If the prototype P is introduced for the first time by the parent of the type inference judgment, we call such parent the Propagation Root for prototype P .

Proof.

A proof by induction on the type inference rule for the parent of the type inference judgment. A complete proof is provided in Appendix C.

Lemma 3.1 allows us to state that we can always find the type inference judgment that in- troduces the prototype for the first time, through simple backtracking through the nodes of the type derivation tree. The subject will be formally explored in more detail later for both error-free (Section 3.6) and erroneous (Section 4.1) type derivation trees.

Lemma 3.1 Existence of the Propagation Roots for type derivation trees.

For any inference judgment of the form (P, Γ w E : T ), where T might or might not have been inferred and P = ?, we can always find the inference judgment (P, Γ w E: T), abbreviated aswP, where Tmight or might not have been inferred, such that

(P, Γ w E : T ) is a subtree that is part of thePw inference judgment, and partial type

information P is first being propagated inwPand it is not part ofwP’s own prototype.

We call the path from the (P, Γ wE : T ) inference judgment down the type derivation tree towP, the Prototype Propagation Path.

Proof.

The proof of Lemma 1 follows directly from Theorem 1, the formulation of the parent of the type inference judgment in Definition 3, and the require- ment on the root of the type derivation tree on having the ? prototype, i.e., a program has to start with no expected type.

Chapter 3. Guided-analysis for type derivation trees

Debugging type variable instantiations

Our informal description of thefoldRightapplication has identified the type variable b and its instantiation to type Li st [⊥] in thefoldRight(xs)(Nil()) partial application as the first node in the type derivation tree where the conflicting expected type has been fully stated. This is an important step in the analysis of our motivating example, yet not the final one. The precise debugging technique would tell which of the type constraints (superscript 9 in Figure 3.4) were used in the instantiation of the type variable b, and how do they relate to the inferred type of the argumentNil(), LIST[].

If we were only interested in the source of the inherited expected type, then reporting the argumentNil(), as the source of type constraints would be correct. However, rather than treating the (type mismatch) failure as a black box, we can gain much more information from taking into account the failed subtyping derivation tree. Since we assume that Li st s in our encoding are covariant, we know that the actual mismatch originates from its type ar- guments:

I nt <: ⊥ LIST[I nt] <: LIST[]

The premise of such failed subtyping derivation tree, provides further information on how to narrow down the analysis of the subtree that instantiates the type variable b - we can fo- cus on tracking the origin of type element⊥ in from the LIST[]type rather than LIST[]. This in turn implies that the analysis of the type derivation trees has to understand how the type parameter a (superscript 7) from type f or al l a. ()→ LIST[a]was instantiated to type⊥ (superscript 8) in the applicationNil(). Careful look at the inference rule(app)in ([?/

b] b, Γ wN i l () : LIST[]) reveals that even though constraints are collected for the type

variable a (superscript 7), none of them are relevant for the inference of the most optimal type, and in consequence, the⊥ type, the true source of the type error is inferred. That is the level of detail, in terms of finding minimal explanations of types, our analysis aims to achieve. Similarly as before, our informal explanation has only relied on how type inference rules infer the instantiations of the type parameters and propagate the elements of types, rather than being specific to thefoldRightapplication. We exploit this insight in two ways:

• We are able to locate the smallest fragment of the type derivation that encapsulates the node where the error is reported to the user and all the nodes that either propagate or introduce the conflicting type for the first time.

• By identifying the nodes of the type derivation that introduce types for the first time, we can suggest opportunistic source code fixes at program locations associated with such nodes.

For example, a naive, non-minimal analysis could suggest to the user to annotate the complete argument in the application with an explicit type annotation, e.g., Nil() :

In document Decrypting Local Type Inference (Page 50-55)