den123
den123

Reputation: 801

Scala, covariant and bounding: difference in function

Following trait declares 2 functions: f1 and f2

trait Test[+T] {
    def f1[U >: T](a: U): Boolean
    def f2[U](a: U): Boolean
  }

Are they same? If not, what is is a difference?

Upvotes: 2

Views: 154

Answers (2)

Joe K
Joe K

Reputation: 18424

I think they are the same, in terms of behavior. Both functions need to be implemented for any type. But types are just as much about giving the compiler more information to help you prevent bugs and such, and I can think of some strange corner cases where one might be preferable to the other. For example:

class Foo
class Bar extends Foo

object BarChecker extends Test[Foo] {
  def f1[U >: Foo](u: U): Boolean = {
    if (u.isInstanceOf[Bar]) {
      true
    } else {
      throw new RuntimeException("fail!")
    }
  }
  def f2[U](u: U): Boolean = {
    if (u.isInstanceOf[Bar]) {
      true
    } else {
      throw new RuntimeException("fail!")
    }
  }
}

val x = BarChecker.f1[Foo](_) // can't do BarChecker.f1[Bar](_)
x(new Bar) // true
x(new Foo) // runtime exception
val y = BarChecker.f2[Bar](_)
y(new Bar) // true
y(new Foo) // compile error

Of course you could fix this with:

val x: Bar => Boolean = BarChecker.f1[Foo](_)

But my point is different type signatures can have different effects on which errors are possible or likely to be made when using them.

Upvotes: 1

Ra Ka
Ra Ka

Reputation: 3055

In term of execution, yes they are same. Both take parameter of type U, and return result of type Boolean. However, in term of type parameter, they are not. Method f1 have a lower bound type parameter however method f2 don't.

So, what does that mean? In case of method f2, you can provide any type parameter. In case of method f1, you can only provide type parameter which is equal to type T or which is superType of T. For example:

class Foo1(name:String)
class Foo2(name:String) extends Foo1(name)
class Foo3(name:String) extends Foo2(name)

class Test[+T] {
    def f1[U >: T](a: U): Boolean = true
    def f2[U](a: U): Boolean = true
  }

val obj: Test[Foo2] = new Test[Foo2]

val foo1: Foo1 = new Foo1("1")
val foo2: Foo2 = new Foo2("2")
val foo3: Foo3 = new Foo3("3")

//F2 execute on any type you provide.
testInstance.f2[Foo1](foo1)
testInstance.f2[Foo2](foo2)
testInstance.f2[Foo3](foo3)

testInstance.f1[Foo2](foo2)     //Foo2 is equal to type T.
testInstance.f1[Foo1](foo1)     //Foo1 is supertype of T - Foo2.
testInstance.f1[Foo3](foo3)     //Does not compile, because Foo3 is not superType of T - Foo2.

In fact, in Scala, in case of Co-variance annotation [+T], you must define an lower bound. Following will fail:

class Test[+T] {
    def f1(a: T): Boolean = true        //This will fail. 
    def f2[U](a: U): Boolean = true
  } 

Upvotes: 1

Related Questions