Nathan
Nathan

Reputation: 1540

Can a Scala for-yield return None if I pass in an Option to it?

I have the following for-yield loop which takes in a boolean and should either yield Some(string) or None, depending on the boolean:

val theBoolean = false

val x: Option[String] =
for {
  theArg <- theBoolean
} yield {
  if (theArg) {
    "abc"
  } else {
    None
  }
}

This works great if theBoolean is actually a Boolean like false. However if I wanted to pass in an Option[Boolean]:

val theBoolean = Some(false)

it seems like Scala automatically applies a Some() wrapper to the None return - I get a complaint that "Expression of type Option[Serializable] doesn't conform to expected type Option[String]" (with None being the Serializable). The yield is perfectly happy with the same string return though (it doesn't become an Option[Option[String]]

How would I return a None in this case?

Upvotes: 11

Views: 9894

Answers (3)

Gabriele Petronella
Gabriele Petronella

Reputation: 108091

A for-comprehension is just syntactic sugar for a series of flatMap, map and filter. Let's desugar your code then:

val theBoolean = Some(false)

val x = theBoolean.map { theArg =>
  if (theArg) {
    "abc"
  } else {
    None
  }
}

As you can see, you're just mapping over the value of the Option, so you'll either return Some(abc), Some(None) or None (in case theBoolean is already None).

The lowest common type of None and "abc" is java.Serializable, so that's why the type of x is inferred as Option[Serializable], which as meaningless as Option[Any].

Possible solutions are:

  • using a flatMap

    theBoolean.flatMap(theArg => if (theArg) Some("abc") else None)
    

    or even shorter

    theBoolean.flatMap(if (_) Some("abc") else None)
    
  • filtering and mapping

    theBoolean.withFilter(identity).map(_ => "abc")
    

Where I used identity since you're testing the value itself.

Clearly you can always leverage the syntactic sugar provided by a for-comprehension, although it doesn't really make a difference in this case

for {
  theArg <- theBoolean
  if theArg
} yield "abc"

Upvotes: 19

Falmarri
Falmarri

Reputation: 48559

Instead of a for comprehension it sounds like you want a flatMap instead of a for comprehension.

scala> Some(false).flatMap(if (_) Some("abc") else None)   
res4: Option[String] = None

Upvotes: 1

Ashalynd
Ashalynd

Reputation: 12563

You are right, everything within the yield block is wrapped into an Option (that's how for-comprehensions for options work). In general, for-comprehensions describe what should be done with the content that can be found inside the monad on which for-comprehension is invoked, but the end result (for the world outside of the yield block) is still a monad of the same type (e.g. Option, Try or List).

On yet more general note: there are a lot of descriptions about what a monad is. You can assume that a monad is that infamous Schrödinger box and you are pondering what could happen with that cat hidden there, but all this still remains a possibility because the box isn't open yet.

A possible way to do what you want:

val theBoolean = false

val x: Option[String] =
for {
  theArg <- theBoolean if theArg
} yield {
  "abc"
}

Upvotes: 1

Related Questions