Reputation: 1142
I have stumbled across something in Scala that really confuses me. It seems that argument types for higher order functions are not polymorphic. Below is a demonstration of what I mean:
class A
class B extends A
def call(f: A => A):A = f(new A)
def A2A(a: A): A = new A
def A2B(a: A): B = new B
def B2A(b: B): A = new A
def B2B(b: B): B = new B
call(A2A) // Works
call(A2B) // Works
call(B2A) // Error!
call(B2B) // Error!
The error message is the same for both calls:
<console>:12: error: type mismatch;
found : B => A
required: A => A
call(new B, B2A)
<console>:12: error: type mismatch;
found : B => B
required: A => A
call(new B, B2B)
A2A and A2B work, so it's not the return type that's causing the problem, it must be the argument type. Is this something to do with type erasure?
I don't think there's anything logically wrong with what I'm trying to do (maybe there is?) so I suspect it's a quirk with the type system?
EDIT
Thanks for the answers! It turns out this is my faulty logic after all. The problem is with the definition of call
. It expects a function that takes an A
(or subtype) as an argument.
B2A
and B2B
both take a B
(or subtype) and are therefore not compatible with call
's definition.
I solved this problem by introducing a trait that A
extends from called ALike
and changed the definition of call
so that it takes any argument that has ALike
as a supertype.
trait ALike
class A extends ALike
class B extends A
def call[T <: ALike](t: T, f: T => A): A = f(t)
def A2A(a: A): A = a
def A2B(a: A): B = new B
def B2A(b: B): A = new A
def B2B(b: B): B = b
call(new A, A2A)
call(new A, A2B)
call(new B, B2A)
call(new B, B2B)
Upvotes: 1
Views: 467
Reputation: 3455
call(f) is expecting a function that can take in an A (or any subtype) and return an A (or any subtype). The reason 1 and 2 work is that your function definition can take A or any subtypes of A, and in 1, returns A (satisfying the condition of being A) or in 2, returns B (satisfying the condition of being a subtype of A).
The reason the latter two don't work is that you're passing in a function which can only take a B and return A or B. To see why this is a problem, assume you've got class C extends A. B2A won't be able to handle C, which means that call(f), by definition, won't be able to handle the input C, despite being a subtype of A. This is a compile error due to the definition of call.
Upvotes: 3
Reputation: 9820
The arguments of a Scala Function
are contravariant. Which in the case of A => A
means that you can create a function which accepts a super type of A
but not a function which accepts a sub type like B
.
You could define a function which takes Any
:
def Any2A(any: Any) = new A
def Any2B(any: Any) = new B
call(Any2A) // A = A@b5bddc9
call(Any2B) // A = B@62c1c65f
Function1[-T1, +R]
The argument of Function1
is contravariant and the result is covariant, which means that in a function of type A => A
:
B
when an A
is the result type (covariant) : A2B
B
when an A
is the type of the parameter (B2A
and B2B
), but you can pass a super type of A
like Any
(Any2A
and Any2B
).Some explanation about the contravariance (and covariance) of Function
can be found in Programming in Scala, 1ed
Upvotes: 3