简体   繁体   中英

Variance of Scala List map function

I have a question that's been bugging me. Lists in Scala are covariant ( List[+A] )

Let's say we have these classes:

class A  
class B extends A

The map function of List[B] takes a function f: B => C

But I can also use a f: A => C which is a subclass of f: B => C
and it totally makes sense.

What I am currently confused by is that the map function should accept only functions that are superclasses of the original function (since functions are contravariant on their arguments), which does not apply in the example I've given.

I know there's something wrong with my logic and I would like to enlightened.

Your error lies in the assumption that map(f: A => C) should only accept functions that are superclasses of A => C .

While in reality, map will accept any function that is a subclass of A => C .

In Scala, a function parameter can always be a subclass of the required type.

The covariance of A in List[A] only tells you that, wherever a List[A] is required, you can provide a List[B] , as long as B <: A .

Or, in simpler words: List[B] can be treated as if it was a subclass of List[A] .

I have compiled a small example to explain these two behaviours:

class A  
class B extends A

// this means: B <: A

val listA: List[A] = List()
val listB: List[B] = List()

// Example 1: List[B] <: List[A]
// Note: Here the List[+T] is our parameter! (Covariance!)

def printListA(list: List[A]): Unit = println(list)

printListA(listA)
printListA(listB)

// Example 2: Function[A, _] <: Function[B, _]
// Note: Here a Function1[-T, +R] is our parameter (Contravariance!)

class C

def fooA(a: A): C = ???
def fooB(b: B): C = ???

listB.map(fooB)
listB.map(fooA)

Try it out!

I hope this helps.

As you already suspected, you are mixing up things here.

On the one hand, you have a List[+A] , which tells us something about the relationships between List[A] and List[B] , given a relationship between A and B . The fact that List is covariant in A just means that List[B] <: List[A] when B <: A , as you know already know.

On the other hand, List exposes a method map to change its "contents". This method does not really care about List[A] , but only about A s, so the variance of List is irrelevant here. The bit that is confusing you here is that there is indeed some sub-typing to take into consideration: map accepts an argument (a A => C in this case, but it's not really relevant) and, as usual with methods and functions, you can always substitute its argument with anything that is a subtype of it. In your specific case, any AcceptedType will be ok, as long as AcceptedType <: Function1[A,C] . The variance that matters here is Function 's, not List 's.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM