Is PartialFunction orElse looser on its type bounds than it should be?

岁酱吖の 提交于 2019-12-05 09:14:15

You're getting this upside down.

You can always pass to a method requiring a parameter of type A any argument of type B <: A, i.e. any subtype of A. That is if you have

def foo(a: Animal)

you can pass a Dog to foo, because Dog <: Animal.

In the same way, if you have

def foo(l: List[Animal])

you can pass a List[Dog] to it, because List is covariant with its type parameter and since Dog <: Animal, then List[Dog] <: List[Animal]

Now if you have

def foo(pf: PartialFunction[String, String])

you can pass a PartialFunction[Any, String], because PartialFunction is contravariant with the first type parameter and covariant with the second. Since Any >: String, then PartialFuncion[Any, String] <: PartialFunction[String, String].

Now, for the type bounds, the compiler will try to infer A1 and B1, such that

  • A1 is subtype of A
  • B2 is subtype of B

To do so, it will look for:

  • the greatest common subtype of Any and String, since A and A1 are in contravariant position
  • the least common supertype of String and String, since B and B1 is covariant position

Results

  • A1String
  • B1String

The case in which you compose a PartialFunction[String, String] with a PartialFunction[Int, Int] is an odd-looking case of the previous example, in which:

  • the greatest common subtype of String and Int is String with Int, i.e. the interesection of the two types, which is subtype of both (in this case is pretty much as saying Nothing: being both a String and an Int doesn't seem very likely)
  • the least common supertype of String and Int is Any

therefore

val a: PartialFunction[String, String] = ...
val b: PartialFunction[Int, Int] = ...
a orElse b // PartialFunction[String with Int, Any] // as expected, although not very useful...

I was going to say that PartialFunction[Any, String] is a subtype of PartialFunction[String, String] because of contra-variance if I understand correctly. This would explain the behavior described in your question before the update, but you got me all mixed up with this union type stuff.

I don't even know what the hell String with Int means!

Kigyo

This is of course vague and only my humble opinion. Suggestions and comments are appreciated.

Taking from this SO question. (How to know if an object is an instance of a TypeTag's type?)

import scala.reflect.runtime.universe._
implicit class MyInstanceOf[U: TypeTag](that: U) {
  def myIsInstanceOf[T: TypeTag] = 
    typeOf[U] <:< typeOf[T]
}

We have a proper way to check isInstanceOf without erasure.

val b: PartialFunction[Any, String] = { case _ => "default" }
b.myIsInstanceOf[PartialFunction[String, String]] //true

And it only makes sense. If you have a function from Any => String, then it accepts any input. So it also accepts String input. That's why it can also be treated as a Function from String => String. Basically it can be treated as T => String for any T.

So in the end the compiler agrees on A -> String and A1 -> String.

 a.orElse[String,String](b) //works

Edit: Final thoughts

You should not think of A1 <: A as a restriction. It will only infer the type of the resulting PartialFunction. There is no way that orElse cannot be applied. Both PF involved are contra-variant on A and therefore a common subtype for BOTH can always be found that satisfies A1 <: A.

I think an analogy would be addition of fractions, where you think, oh, they have not a common denominator and therefore can not be added. Both fractions can be adjusted (or: seen differently) to have a common denominator. The compiler although wants to find the smallest common denominator and not resort to the easy way with multiplying with the other denominator. (A with A')

For the other type B it's the same. Both are co-variant and therefore a common super type can also always be found. Any in the worst case.

You already typed this in, but:

scala> val a: PartialFunction[String, String] = { case "a" => "b" }
a: PartialFunction[String,String] = <function1>

scala> val b: PartialFunction[Any, String] = { case 1 => "one" }
b: PartialFunction[Any,String] = <function1>

scala> a orElse b
res0: PartialFunction[String,String] = <function1>

scala> a orElse[String,String] b
res1: PartialFunction[String,String] = <function1>

scala> a orElse[Any,String] b
<console>:10: error: type arguments [Any,String] do not conform to method orElse's type parameter bounds [A1 <: String,B1 >: String]
              a orElse[Any,String] b
              ^

scala> import reflect.runtime._ ; import universe._
import reflect.runtime._
import universe._

scala> typeOf[PartialFunction[Any,String]] <:< typeOf[PartialFunction[String,String]]
res3: Boolean = true

Because of the contravariant type param, you can use PF[Any, String] here.

To answer the question, where does it say what it will pick?

http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#local-type-inference

E.g., it promises to infer a maximal A1 in contravariant position, but still conforming to the constraint <: A.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!