Say you have arguments A1 >: A2 (contra-variant), and return types B1 <: B2 (covariant).
The corresponding functions are such that:
A1 => B1 <: A2 => B2
Sometimes, this makes sense to me - I will grasp it in a flash of lateral thinking. And then a few days later it is lost to me until I struggle through it for another hour or so.
Using an example from Odersky's Scala MOOC (this is not an assignment spoiler), Empty and NonEmpty are both subtypes of IntSet.
I understand that you cannot pass an IntSet into a function expecting a NonEmpty, since the IntSet might be an Empty. Therefore the latter (A2 => B2) cannot be a subtype of the former (A1 => B1). And I understand that you can pass a NonEmpty into a function expecting an IntSet, apparently because the function will only be calling IntSet-specific methods that all subtypes have implemented. Therefore the former can be a subtype of the latter.
Similarly, a function that returns an IntSet cannot be a subtype of a function that returns a NonEmpty, because that returned IntSet cannot necessarily do everything the NonEmpty can do. Conversely, a function that returns a NonEmpty can be a subtype of a function that returns an IntSet, since that returned NonEmpty will conform to the IntSet contract.
But what I'm having trouble grasping is, if you can pass a NonEmpty into a function expecting an IntSet, and if that function were a subtype of a function expecting a NonEmpty (ostensibly because it is calling NonEmpty-specific methods in its implementation), then why would you ever want to use that subtyped function? Does anyone have examples of this being useful? Why use subtyped functions?
To me it just seems dangerous to pass in an IntSet if you know that it is going to call NonEmpty-specific functionality.