Is PartialFunction or Else freer from type constraints than it should be?

Define a PartialFunction[String, String] and a PartialFunction[Any, String]

Now, given the definition of orElse

 def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1] 

I expect that I can’t put them together, because

A β†’ String
A1 β†’ Any

and therefore, A1 <: A (i.e., Any <: String ) is not evaluated.

Suddenly, I can compose them and get the PartialFunction[String, String] defined in the entire String domain. Here is an example:

 val a: PartialFunction[String, String] = { case "someString" => "some other string" } // a: PartialFunction[String,String] = <function1> val b: PartialFunction[Any, String] = { case _ => "default" } // b: PartialFunction[Any,String] = <function1> val c = a orElse b // c: PartialFunction[String,String] = <function1> c("someString") // res4: String = some other string c("foo") // res5: String = default c(42) // error: type mismatch; // found : Int(42) // required: String 

Also, if I explicitly provide parameters of type orElse

 a orElse[Any, String] b // error: type arguments [Any,String] do not conform to method orElse type parameter bounds [A1 <: String,B1 >: String] 

the compiler finally makes some sense.

Is there any systemic witchcraft that I skip that results in b being a valid argument for orElse ? In other words, how is it that A1 output as String ?

If the compiler outputs A1 from b , then it should be Any , so where does the output chain leading to String begin?


Update

After playing with REPL, I noticed that orElse returns the intersection type A with A1 if the types do not match. Example:

 val a: PartialFunction[String, String] = { case "someString" => "some other string" } // a: PartialFunction[String,String] = <function1> val b: PartialFunction[Int, Int] = { case 42 => 32 } // b: PartialFunction[Int,Int] = <function1> a orElse b // res0: PartialFunction[String with Int, Any] = <function1> 

Since (String with Int) <:< String this works, although the resulting function is almost unusable. I also suspect that String with Any unified in Any , given that

 import reflect.runtime.universe._ // import reflect.runtime.universe._ typeOf[String] <:< typeOf[String with Any] // res1: Boolean = true typeOf[String with Any] <:< typeOf[String] // res2: Boolean = true 

So why mixing String and Any results in String .

As the saying goes, what happens under the hood? Under what logic are inappropriate types combined?

Update 2

I reduced the problem to a more general form:

 class Foo[-A] { def foo[B <: A](f: Foo[B]): Foo[B] = f } val a = new Foo[Any] val b = new Foo[String] a.foo(b) // Foo[String] Ok, String <:< Any b.foo(a) // Foo[String] Shouldn't compile! Any <:!< String b.foo[Any](a) // error: type arguments [Any] do not conform to method foo type parameter bounds [A <: String] 
+5
types scala partialfunction
source share
4 answers

You will turn it upside down.

You can always move on to a method that requires a parameter of type A any argument of type B <: A , that is, of any subtype of A That is, if you have

 def foo(a: Animal) 

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

In the same way if you

 def foo(l: List[Animal]) 

you can pass it List[Dog] , because List 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 PartialFunction[Any, String] , because PartialFunction is contravariant with the parameter of the first type and covariant with the second. Since Any >: String , then PartialFuncion[Any, String] <: PartialFunction[String, String] .

Now, for type boundaries, the compiler will try to infer A1 and B1 , which

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

To do this, he will look for:

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

results

  • A1 β†’ String
  • B1 β†’ String

The case when you create PartialFunction[String, String ] with PartialFunction[Int, Int] is an odd example of the previous example, in which:

  • the largest common subtype of String and Int is String with Int , i.e. interesection of two types, which is a subtype of both (in this case, it pretty much means Nothing : like a String and a Int does not seem very likely)
  • the smallest common supertype of String and Int is Any

so

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

I was going to say that PartialFunction[Any, String] is a subtype of PartialFunction[String, String] due to a contradiction, if I understand correctly. This explains the behavior described in your question before the upgrade, but you have all mixed me up with this stuff like union.

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

+4
source share

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

Take from this question SO. ( How do I know if an object is an instance of a TypeTag type? )

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

We have the right way to check isInstanceOf without erasing.

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

And that makes sense. If you have a function from Any => String , it accepts any input. Therefore, it also accepts String input. Therefore, it can also be considered as a function from String => String . Basically, for any T it can be considered T => String .

So, at the end, the compiler agrees with A -> String and A1 -> String .

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

Edit: Final thoughts

You should not think of A1 <: A as a constraint. This will only result in the type of the resulting PartialFunction result. You cannot use orElse . Both PFs involved are conflicting on A , and therefore one can always find a common subtype for BOTH that satisfies A1 <: A

I think that an analogy would be the addition of fractions where you think oh, they do not have a common denominator and therefore cannot be added. Both fractions can be adjusted (or: visible in different ways) to have a common denominator. Although the compiler wants to find the lowest common denominator and not resort to easy paths with multiplication by another denominator. ( A with A' )

For another type B this is the same. Both options are co-options, and so you can always find a common super-type. Any in the worst case.

+1
source share

You have already entered this, 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 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 

Due to the contravariant type parameter, you can use PF [Any, String] here.

To answer the question, where will he say what he chooses?

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

For example, he promises should output the maximum A1 in a contravariant position, but still consistent with the constraint <: A.

+1
source share

All Articles