Search code examples
scalamethodstype-parametertraits

Scala trait and its methods’ parametrization


In my application I want to use such a trait:

trait HasBuffer[+A] {

    var items = Buffer[A]()

    def add[A](item: A) { items += item }
    def remove[A](item: A) { items -= item }
    def set(is: Buffer[A]) { items = is }
    def clear { items clear }
}

Classes that inherit this trait should be able to buffer any instances of classes who are children of the class A. However on both the add and remove methods the compiler complains about the item being added or removed from the items that "type mismatch; found : item.type (with underlying type A) required: A". How should I understand this? What is my mistake here and what to do?


Solution

  • You are parameterizing the methods with another type parameter A that differs from the one of your class definition. Here's the version you wrote with renamed parameters:

    trait HasBuffer[+A] {
    
        var items = Buffer[A]()
    
        def add[B](item: B) = items += item
        def remove[B](item: B) { items -= item }
        def set(is: Buffer[A]) { items = is }
        def clear { items clear }
    }
    

    Now it should be clear, why the compiler rejects this.

    Instead you can simply write the methods like this:

    def add(item: A) = items += item
    def remove(item: A) { items -= item }
    

    However, then you will still receive compiler errors stating that covariant type A occurs in contra- and invariant positions.

    The point of being covariant is that if you expect a HasBuffer[AnyVal] one may pass in a HasBuffer[Int]. However, if you expect AnyVal and use that type for the add method as well, you would be able to add a completely different type to your HasBuffer[Int]. Hence, the compiler rejects this.

    Instead, you will have to provide a lower bound on the type parameter like this:

    trait HasBuffer[+A, V <: A] {
    
        var items  = Buffer[V]()
    
        def add(item: V) = items += item
        def remove(item: V) { items -= item }
        def set(is: Buffer[V]) { items = is }
        def clear { items clear }
    }
    

    With this you may now have methods like the following:

    def doSomething[X <: AnyVal](b : HasBuffer[AnyVal, X], e : X) = b.add(e)
    

    This method will work on all sorts of HasBuffer type parameter combinations that satisfy the required lower bound.

    Mentally compare this with the idea of not providing a lower bound. Then the method would have become something like this:

    // doesn't make much sense!
    def doSomething(b : HasBuffer[AnyVal], e : AnyVal) = b.add(e)
    

    If you call this with an object of type HasBuffer[Int] and a Double it'll be all happy. You probably won't be happy lateron though, when you find your buffer that should contain only Ints now contains a Double.