Working on an example from Functional Programming in Scala, I'm working on Option#map2
:
override def map2[A, B, C](fa: Option[A],fb: Option[B])(f: (A, B) => C): Option[C] = {
(fa, fb) match {
case (None, _) => None
case (_, None) => None
case (_, _) => Some(f(fa.get, fb.get)) // runtime-safe get calls
}
}
Is the above implementation reasonable? I presumed that, if either fa
or fb
were None
, then so should be the returned value. Pattern matching seemed the most clean to me, but perhaps there's a cleaner or more concise way to write this method?
1 Answer 1
Your code is correct - it should work nicely.
The first thing that I would improve on is the // runtime-safe get calls
. This is an anti-pattern in Scala and is generally discouraged. In this case you can use pattern matchings powerful extractors to get the value the Option
is storing:
case (Some(a), Some(b)) => Some(f(a, b))
But instead of pattern matching the most elegant way to solve this problem is to use for comprehensions. It is functionally the same as your code:
override def map2[A, B, C](fa: Option[A], fb: Option[B])(f: (A, B) => C): Option[C] = {
for {
a <- fa
b <- fb
} yield f(a, b)
}
Since Option
is essentially a collection with either zero or one element it can be used in for-comprehensions. The variable a
will only be populated if fa
is a Some
. If either is None
then nothing will be yielded and the result is None
.
-
\$\begingroup\$ Thanks, Akos. Could you please provide a reference for
.get
being an anti-pattern? It makes sense to me, but I'd appreciate a reference for further reading \$\endgroup\$Kevin Meredith– Kevin Meredith2014年04月01日 15:59:59 +00:00Commented Apr 1, 2014 at 15:59 -
1\$\begingroup\$ Sorry, I may have overused the word "anti-pattern" as it is not explicitly stated anywhere. The issue is that
.get
will throw aNoSuchElementException
if theOption
is empty. In this case you know that it won't be empty, but what happens when you or someone refactors it? Will they make sure that the.get
will be called on a code path that is safe? It is generally encouraged (not only in Scala) to avoid this, because you're just setting yourself up for bugs. I would recommend a talk on this topic, it covers the issue and Scala's solution to it quite well: youtu.be/gVXt1RG_yN0 \$\endgroup\$Akos Krivachy– Akos Krivachy2014年04月01日 18:12:46 +00:00Commented Apr 1, 2014 at 18:12 -
\$\begingroup\$ Thanks! I went to NeScala 2014 and was thinking of that video right as I clicked your link! \$\endgroup\$Kevin Meredith– Kevin Meredith2014年04月01日 20:31:46 +00:00Commented Apr 1, 2014 at 20:31
-
\$\begingroup\$ When I use it like this
Option.map2(Some(3), None)(_+_)
it fails why? \$\endgroup\$eguneys– eguneys2015年08月27日 17:36:40 +00:00Commented Aug 27, 2015 at 17:36 -
1\$\begingroup\$ @eguneys The issue is that
None
is essentiallyOption[Nothing]
and the_ + _
then translates toInt + Nothing
. This can't compile asNothing
can't be added withInt
, to fix this useOption.empty[Int]
instead ofNone
! \$\endgroup\$Akos Krivachy– Akos Krivachy2015年08月27日 21:49:50 +00:00Commented Aug 27, 2015 at 21:49