How does the type inferencer work on reduceLeft?
Asked Answered
A

2

3

Further to my other question about reduceLeft, the signature of reduceLeft on Seq is

def reduceLeft [B >: A] (f: (B, A) ⇒ B): B 

and we can call it with expressions such as

List(1,2,3,4) reduceLeft (_ + _)

In this example A is Int, so reduceLeft expects a Function2[B >: Int, Int, B]. Regardless of how reduceLeft works (which is irrelevant), how does the type inferencer know that B has a + method, when it could be of type Any?

Arrears answered 3/12, 2011 at 2:4 Comment(0)
M
4

I think the section 6.26.4 Local Type Inference of the spec sort of explains what's going on. The compiler will look for an optimal type. When the type parameter is contravariant the type chosen will be maximal (in this case Any) and otherwise (invariant or covariant) minimal (in this case Int).

There are a couple examples which I can't really relate to reduceLeft.

What I did notice is the inference seems to happen before looking at the anonymous function passed:

scala> List(1,2).reduceLeft[Any](_.toString + _)
res26: Any = 12

But If I don't help the type inferencer:

scala> List(1,2).reduceLeft(_.toString + _)
<console>:8: error: type mismatch;
 found   : java.lang.String
 required: Int
              List(1,2).reduceLeft(_.toString + _)

Edit, I'm wrong the anonymous function is taken into account, this works:

List(1,2).reduceLeft((_:Any).toString + (_:Any).toString)

There is a compiler -Ytyper-debug option that you can run on:

List(1,2).reduceLeft(_+_)

It will show you that somehow the compiler assumes the expected type of the anonymous function is (Int, Int) => Int, then it proceeds to check the _ + _ against it and succeeds and then infers B as Int. Snippet here:

typed immutable.this.List.apply[Int](1, 2).reduceLeft: [B >: Int](f: (B, Int) => B)B
adapted immutable.this.List.apply[Int](1, 2).reduceLeft: [B >: Int](f: (B, Int) => B)B to ?, undetparams=type B
typing ((x$1, x$2) => x$1.$plus(x$2)): pt = (Int, Int) => Int: undetparams=, 
// some time later 
typed ((x$1: Int, x$2: Int) => x$1.+(x$2)): (Int, Int) => Int
adapted ((x$1: Int, x$2: Int) => x$1.+(x$2)): (Int, Int) => Int to (Int, Int) => Int, 
typed immutable.this.List.apply[Int](1, 2).reduceLeft[Int](((x$1: Int, x$2: Int) => x$1.+(x$2))): Int

I don't know why in absence of type ascription the anonymous function is assumed to be (Int, Int) => Int.

Mutation answered 3/12, 2011 at 4:34 Comment(0)
O
1

If B >: X and the compiler knows X but cannot resolve B it simply assumes B = X.

It is somewhat practical since it only has two options for B and only one is known. So absent knowing which super class it assumes that B is X. You can test the compilers decision making process with the following code.

class Y {
  def bar(y:Y) = this
}
case class X( i: Int ) extends Y {
  def foo(x:X)=X(i+x.i)
}
val t = new Y bar X(7)
val t2 = X(8) bar X(7)
val res = List(X(1),X(2),X(3)) reduceLeft { _ foo _ }
val res2 = List(X(1),X(2),X(3)) reduceLeft { _ bar _ } // will not compile
Overset answered 3/12, 2011 at 4:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.