To answer the syntax questions first:
Scala does in some cases allow a few different notations for calling methods. Furthermore, in many cases, specifying this
is optional.
If we look at the flatMap
method, you’d call it as someOption.flatMap(someFunc)
. In the method body, someOption
will be referred to as this
. To make the code less verbose, a method call like the one to map
, or a variable name, will refer to the method or field on this
, if there isn’t a local variable or method with the same name:
//in flatMap, these would be equivalent:
this.map(f)
map(f)
As the name map
is unambiguous in orElse
, the this
can be left off, the book’s code style is a bit inconsistent here.
The second feature playing into this here is the posibility to write methods like an infix operator: if you have some code of the form obj.method(param)
, you can also write this as obj method param
. This will only work as expected with methods taking one parameter, and the object must be specified (which is why you can’t write map f getOrElse None
, here a this
would be needed for infix notation).
Generally, the use of infix notation is only recommended for symbolic operators and maybe higher order functions (see Infix Notation docs). I generally don’t use it for non-symbolic functions and would recommend to write the flatMap
method as map(f).getOrElse(None)
, as it makes the evaluation order clearer in my opinion.
Now for the semantic question:
The important thing to note here is that the type B
is a type parameter on the methods, so it can be different for two method calls, and different for the map
call from the B
of the flatMap
call. It is a local type variable.
So when you call flatMap
with a function f: A => Option[B]
, the B
for flatMap
is the type inside the returned option. But when you then pass on f
to map
, its parameter B
is separately inferred. To make things a bit clearer, here is an implementation with different variable names per method (it is fully equivalent to the solution from the book):
def map[C](f: A => C): Option[C] =
this match {
case None => None
case Some(v) => Some(f(v))
}
def flatMap[B](f: A => Option[B]): Option[B] =
map(f) getOrElse None
Now in flatMap
, we pass f: A => Option[B]
to map
, which expects a function of type A => C
. This is not a problem, if we set C = Option[B]
: map
will handle the Option[B]
like any other value and wrap it into a Some
. So the result will be a Option[Option[B]]
.
As flatMap
should not return a nested Option
, getOrElse
is used to remove the nesting. If this
was already None
, it returns the given default, which is also None
. If it was a Some[A]
, it will now be a Some[Some[B]]
and getOrElse
will return the inner Some
.