I'm reading Functional Programming in Scala, and in chapter 04 the authors implement Option on their own. Now, when defining the function getOrElse
they use an upper bound to restrict the type of A
to a supertype (if a understood correctly)
So, the definition goes:
sealed trait Option[+A] {
def getOrElse[B >: A](default: => B): B = this match {
case None => default
case Some(a) => a
}
}
So, when we have something like
val a = Some(4)
println(a.getOrElse(None)) => println prints a integer value
val b = None
println(b.getOrElse(Some(3)) => println prints a Option[Integer] value
a
has type Option[Int]
, so A
would be type Int
. B
would be type Nothing
. Nothing
is a subtype of every other type. That means that Option[Nothing]
is a subtype of Option[Int]
(because of covariance), right?
But with B >: A
we said that B
has to be a supertype?! So how can we get an Int
back? This is a bit confusing for me...
Anyone care to try and clarify?
That means that Option[Nothing] is a subtype of Option[Int] (because of covariance), right?
Correct. Option[Nothing]
is an Option[Int]
.
But with B >: A we said that B has to be a supertype?! So how can we get an Int back?
It doesn't have to be a super-type. It just requires A
as a lower-bound. Which means you can still pass Int
to getOrElse
if A
is Int
.
But that doesn't mean you can't pass instances of a sub-class. For instance:
class A
class B extends A
class C extends B
scala> Option(new B)
res196: Option[B] = Some(B@661f82ac)
scala> res196.getOrElse(new C)
res197: B = B@661f82ac
scala> res196.getOrElse(new A)
res198: A = B@661f82ac
scala> res196.getOrElse("...")
res199: Object = B@661f82ac
I can still pass an instance of C
, because C
can be up-cast to B
. I can also pass a type higher up the inheritance tree, and getOrElse
will return that type, instead. If I pass a type that has nothing to do with the type contained in the Option
, then the type with the least upper-bound will be inferred. In the above case, it's Any
.
So why is the lower-bound there at all? Why not have:
def getOrElse[B <: A](default: => B): B
This won't work because getOrElse
must either return the A
that's contained in the Option
, or the default B
. But if we return the A
, and A
is not a B
, so the type-bound is invalid. Perhaps if getOrElse
returned A
:
def getOrElse[B <: A](default: => B): A
This would work (if it were really defined that way), but you would be restricted by the type-bounds. So in my above example, you could only pass B
or C
to getOrElse
on an Option[B]
. In any case, this is not how it is in the standard library.
The standard library getOrElse
allows you to pass anything to it. Say you have Option[A]
. If we pass a sub-type of A
, then it is up-cast to A
. If we pass A
, obviously this is okay. And if we pass some other type, then the compiler infers the least upper-bound between the two. In all cases, the type-bound B >: A
is met.
Because getOrElse
allows you to pass anything to it, many consider it very tricky. For example you could have:
val number = "blah"
// ... lots of code
val result = Option(1).getOrElse(number)
And this will compile. We'll just have an Option[Any]
that will probably cause an error somewhere else down the line.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With