Reputation: 1540
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
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
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
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