Search code examples
scalaparameterization

scala type parameterization confusion


Consider the following example

abstract class Lookup(val code:String,val description:String)

class USState(code:String, description:String, val area:Symbol)
  extends Lookup(code,description)

class Country(code:String, description:String, val postCode:String)
  extends Lookup(code,description)

class HtmlLookupSelect(data:List[Lookup]) {
  def render( valueMaker:(Lookup) => String ) =
    data.map( (l) => valueMaker(l) )
}

val countries = List(
  new Country("US","United States","USA"),
  new Country("UK","Unites Kingdom","UK"),
  new Country("CA","Canada","CAN"))

def lookupValue(l:Lookup) = l.description
def countryValue(c:Country) = c.description + "(" + c.postCode + ")"

val selector = new HtmlLookupSelect(countries) //Doesn't throw an error
selector.render(countryValue) //Throws an error

HtmlLookupSelect expects a list of Lookup objects as constructor parameter. While creating a HtmlLookupSelect object, a list of county objects are passed to it and the compiler doesn't throw an error since it recognizes Country as a subclass of Lookup

But in the next line, when I try to invoke a method with Country as the parameter type(instead of the expected Lookup), I get a Type mismatch error. Why is this happening?


Solution

  • countryValue is a function from Country to String (actually a method that gets eta-expanded into a function, but not relevant now), i.e. a Function1[Country, String].

    render expects a Function1[Lookup, String].

    So the question we want to answer is

    Given Country is a subtype of Lookup

    is Function1[Country, String] a subtype of Function1[Lookup, String]?

    In order to answer that, let's look at the definition of Function1:

    trait Function1[-T1, +R] extends AnyRef
    

    See the -T1? That's the input parameter and the - means that Function1 is contravariant in its input parameter and covariant in its output parameter.

    So, if A <: B (where <: is the subtype relation) and R <: S then

    Function1[B, R] <: Function[A, S]
    

    In your example, Country <: Lookup so

    Function1[Country, String] </: Function[Lookup, String]
    

    In other words a function is a subtype of another when it promises no less (co-variant return type) and it requires no more (contra-variant input type).

    For example: a function that takes an Animal and returns an Apple can be used whenever a function that takes a Dog and returns a Fruit is required. More formally

    Dog <: Animal
    Fruit <: Apple
    Function1[Animal, Apple] <: Function1[Dog, Fruit]
    

    but the opposite isn't true: if your function handles dogs and returns any fruit, it surely can't be used to handle any animal and return an apple.