Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between `Option.fold()()` and `Option.map().getOrElse()`?

Tags:

scala

The Option class has a method named fold(). The docs say:

sealed abstract class Option[+A] 

fold[B](ifEmpty: ⇒ B)(f: (A) ⇒ B): B

Returns the result of applying f to this scala.Option's value if the scala.Option is nonempty. Otherwise, evaluates expression ifEmpty.

The docs continue:

This is equivalent to scala.Option map f getOrElse ifEmpty.

But is this really true? I've been told that under certain circumstances, with values of certain types, there are differences, but never with a decent explanation. What exactly are the situations where these two constructions will behave differently and why?

like image 714
Adam Mackler Avatar asked Jun 16 '18 22:06

Adam Mackler


1 Answers

Option.fold is safer than .getOrElse. You can see the definition for .fold below, where both ifEmpty and f are of type B (introduced only after scala 2.10, probably):

  @inline final def fold[B](ifEmpty: => B)(f: A => B): B =
    if (isEmpty) ifEmpty else f(this.get)

which means you will probably not mess up the data types (exception below):

scala> val data = Option("massive data").fold(-1) { _ => 1 }
data: Int = 1

// but if I try to return different type in either of ifEmpty or f
// compiler will curse me right at my face   
scala> val data = Option("massive data").fold(-1) { _ => "Let me get caught by compiler" }
<console>:17: error: type mismatch;
 found   : String("Let me get caught by compiler")
 required: Int
       val data = Option("massive data").fold(-1) { _ => "Let me get caught by compiler" }
                                                     ^

While getOrElse is not as safe, unless you provide the type (supertype B in following definition) manually.

  @inline final def getOrElse[B >: A](default: => B): B =
    if (isEmpty) default else this.get

which means you can return a different type from getOrElse than what the original value wrapped in Option[A] was.

scala> val data = Option("massive data").map(_ => 1).getOrElse(List("I'm not integer"))
data: Any = 1

// you have to manually mention the type to getOrElse to restrict, 
// which is not that smart in my opinion
scala> val data = Option("massive data").map(_ => 1).getOrElse[Int](List("I'm not integer"))
<console>:17: error: type mismatch;
 found   : List[String]
 required: Int
       val data = Option("massive data").map(_ => 1).getOrElse[Int](List("I'm not integer"))
                                                                    ^

The interesting thing is you can return unit from getOrElse or fold which can introduce bugs in an application unless you catch it in unit tests.

scala> val data = Option("massive data").fold() { _ => 1 }
data: Unit = ()

scala> val data = Option("massive data").map(_ => 1).getOrElse()
data: AnyVal = 1
like image 131
prayagupa Avatar answered Nov 15 '22 09:11

prayagupa