2

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.

Brian McCutchon
  • 8,354
  • 3
  • 33
  • 45
  • 1
    The covariance of the type parameter of `List` has nothing to do with the parameter of `map`. `List[+T]` just tells you that for every place where a `List[A]` is required, it's okay to provide a `List[B]`as long as `B <: A`. – Markus Appel Dec 07 '18 at 09:36
  • 1
    `map`, like any function, will accept any subtype of the required parameter type. And that's true for `Function[A, C] <: Function[B, C]` where `A <: C`. – Markus Appel Dec 07 '18 at 09:45
  • These two statements do not contradict each other at all. – Markus Appel Dec 07 '18 at 09:46
  • 1
    `map()` is a method that takes a parameter of type X, and, like all methods, it will accept a parameter that is a _sub-type_ of X. The fact that X is a function type doesn't change that. And if functions are contra-variant on _their_ arguments, that's of no concern to `map()`. The "accept X or sub-X" rule still applies. – jwvh Dec 07 '18 at 10:41
  • Thanks for your answer. I shouldn't mention about the covariance of Lists, since it was not related with my question. I was basically confused with the `flatMap` method of `Either[+A, +B]`, where the 'B' there is in covariant position and it was ok, and it seemed to me that the `flatMap` can accept functions with subclasses as parameters – Thrasos Thrasyvoulou Dec 07 '18 at 17:44

2 Answers2

3

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.

Markus Appel
  • 3,138
  • 1
  • 17
  • 46
0

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 As, 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.

mdm
  • 3,928
  • 3
  • 27
  • 43
  • Thanks for your answer. I shouldn't mention about the covariance of Lists, since it was not related with my question. I was basically confused with the flatMap method of Either[+A, +B], where the 'B' there is in covariant position and it was ok, and it seemed to me that the flatMap can accept functions with subclasses as parameters – Thrasos Thrasyvoulou Dec 07 '18 at 17:44