Option.fold in scala 2.10

2020-02-12 14:03发布

问题:

In the following session with scala 2.10.0-M7:

scala> trait A
defined trait A
scala> class B extends A
defined class B
scala> class C extends A
defined class C
scala> Some(0).fold(new B){_=>new C}
<console>:11: error: type mismatch;
 found   : C
 required: B
              Some(0).fold(new B){_=>new C}

I would expect the compiler find the common supertype (namely A) rather than complaining. Is it the general type inference limitation, or the consequence of the way Option.fold is defined?

Thank you.

回答1:

The problem results of a combination of Scalas type inference algorithm and the way how Option.fold is defined.

Scalas type inference works from left to right, this means that it starts with the leftmost symbol to search for a possible type for an expression. Further, for method parameter lists this means, that a generic type is bound to the type filled in by the leftmost parameter list:

scala> def meth[A](a1: A, a2: A) = (a1, a2)
meth: [A](a1: A, a2: A)(A, A)

scala> meth(1, "")
res7: (Any, Any) = (1,"")

scala> def meth[A](a1: A)(a2: A) = (a1, a2)
meth: [A](a1: A)(a2: A)(A, A)

scala> meth(1)("")
<console>:10: error: type mismatch;
 found   : String("")
 required: Int
              meth(1)("")
                      ^

As one can see, in the first case Any is inferred, whereas in the second case a compiler error is thrown because the type of A is bound by the first parameter list and the second one can't change it any more.

But in order to get he method call in the question to work, the type of the resulting Option may not defined until the second parameter list is reached. Because this requires a right to left type inference hence the error. This is somewhat identical to List.fold:

scala> List(1).foldLeft(Nil)((xs,x) => x::xs)
<console>:8: error: type mismatch;
 found   : List[Int]
 required: scala.collection.immutable.Nil.type
              List(1).foldLeft(Nil)((xs,x) => x::xs)
                                               ^

To get the code to work one has to explicitly specify the type of the resulting collection, see @rks answer for an example.

See the discussion here for the full explanation why it is defined as it is defined. In short: Option follows the design of collections in a lot of respects - thus it is more clear when it behaves the same way as collections do.



回答2:

I feel like sschaef version isn't precise enough. I don't know scala much (in fact, I never used it) but I don't think it depends on the way the function is implemented. I also didn't understood the "the typer goes from left to right" thing either.

I don't have a recent version of Scala, so I can't test on your example venechka, but I think the typing error/limitation can be circumvented by adding some type annotations. For exemple here is sschaef's example :

scala> List(1).foldLeft(Nil)((xs,x) => x::xs)
<console>:8: error: type mismatch;
 found   : List[Int]
 required: scala.collection.immutable.Nil.type
              List(1).foldLeft(Nil)((xs,x) => x::xs)
                                               ^

scala> List(1).foldLeft(Nil : List[Int])((xs,x) => x::xs)
res1: List[Int] = List(1)

And I believe you could do the same on your example by doing something like :

Some(0).fold(new B : A){_=>new C}

Again, I think it's a limitation of Scala typer (probably due to the presence of subtyping), but I would have to look around before affirming that strongly.

Anyway, adding type annotations here and there should do the trick for you, so enjoy!

EDIT: Oh, sschaef edited his answer to have some explanations which probably will invalidate what I'm saying about the reason of this behavior. But it doesn't change the fact that type annotations will solve your problem. So I will just let this message in place.



回答3:

This is a general limitation of the type inference algorithm. The folding across lists has the same limitation. To quote Programming in Scala:

Note that both versions of flatten require a type annotation on the empty list that is the start value of the fold. This is due to a limitation in Scala's type inferencer, which fails to infer the correct type of the list automatically.

Scala's type inference algorithm works incrementally across methods with multiple parameter lists. Types specified in the first can be used for inference in the second, the second in the third, etc. As outlined in the style guide, this allows you to use simpler syntax in the fold functions since the inference engine knows the type of the list and the accumulator's type from the first argument list.

However, since it works incrementally across successive parameter lists the inference engine won't go back and update the return type (which is the same as the accumulator type) after inferring the type of the function argument to fold. Instead you get a type error.

In your example above, just give a type annotation on your accumulator value and you'll be good:

Some(0).fold(new B: A){_=>new C}