Reputation: 24841
Suppose I have two Options and, if both are Some, execute one code path, and if note, execute another. I'd like to do something like
for (x <- xMaybe; y <- yMaybe) {
// do something
}
else {
// either x or y were None, handle this
}
Outside of if
statements or pattern matching (which might not scale if I had more than two options), is there a better way of handling this?
Upvotes: 17
Views: 3795
Reputation: 1918
I think the key point here is to think in term of types as what you want to do. As I understand it you want to iterate over a list of Option pairs and then do something based on a certain condition.
So the interesting bit of your question would be, what would the return type look like you would except? I think it would look something like this: Either[List[Option], List [Option,Option]]
.
On the error side (left) you would accumulate the option which was paired with a None (and was left alone so to speak). On the right side you sum the non empty options which represent your successful values. So we would just need a function which does exactly that. Validate each pair and accumulate it according to it's result( success - failure).
Some links to implement what I described:
Upvotes: 0
Reputation: 61666
Starting Scala 2.13
, we can alternatively use Option#zip
which concatenates two options to Some tuple of their values if both options are defined or else None:
opt1 zip opt2 match {
case Some((x, y)) => "x and y are there"
case None => "x and/or y were None"
}
Or with Option#fold
:
(opt1 zip opt2).fold("x and/or y were None"){ case (x, y) => "x and y are there" }
Upvotes: 2
Reputation: 37633
Why would something like this not work?
val opts = List[Option[Int]](Some(1), None, Some(2))
if (opts contains None) {
// Has a None
} else {
// Launch the missiles
val values = opts.map(_.get) // We know that there is no None in the list so get will not throw
}
Upvotes: 4
Reputation: 3063
The traverse
function in Scalaz generalises your problem here. It takes two arguments:
T[F[A]]
A => F[B]
and returns F[T[B]]
. The T
is any traversable data structure such as List
and the F
is any applicative functor such as Option
. Therefore, to specialise, your desired function has this type:
List[Option[A]] => (A => Option[B]) => Option[List[B]]
So put all your Option
values in a List
val z = List(xMaybe, yMaybe)
Construct the function got however you want to collection the results:
and call traverse
This programming patterns occurs very often. It has a paper that talks all about it, The Essence of the Iterator Pattern.
note: I just wanted to fix the URL but the CLEVER edit help tells me I need to change at least 6 characters so I include this useful link too (scala examples):
http://etorreborre.blogspot.com/2011/06/essence-of-iterator-pattern.html
Upvotes: 6
Reputation: 127
If you don't know the number of values you are dealing with, then Tony's answer is the best. If you do know the number of values you are dealing with then I would suggest using an applicative functor.
((xMaybe |@| yMaybe) { (x, y) => /* do something */ }).getOrElse(/* something else */)
Upvotes: 3
Reputation: 40461
Very close to your syntax proposal by using yield
to wrap the for
output in an Option:
val result = {
for (x <- xMaybe; y <- yMaybe) yield {
// do something
}
} getOrElse {
// either x or y were None, handle this
}
The getOrElse
block is executed only if one or both options are None.
Upvotes: 27
Reputation: 340733
You said you want the solution to be scalable:
val optional = List(Some(4), Some(3), None)
if(optional forall {_.isDefined}) {
//All defined
} else {
//At least one not defined
}
EDIT: Just saw that Emil Ivanov's solution is a bit more elegant.
Upvotes: 2
Reputation: 13924
For scaling to many options, try something along these lines:
def runIfAllSome[A](func:(A)=>Unit, opts:Option[A]*) = {
if(opts.find((o)=>o==None) == None) for(opt<-opts) func(opt.get)
}
With this, you can do:
scala> def fun(i:Int) = println(i)
fun: (i: Int)Unit
scala> runIfAllSome(fun, Some(1), Some(2))
1
2
scala> runIfAllSome(fun, None, Some(1))
scala>
Upvotes: 0
Reputation: 4475
You could pattern match both Options
at the same time:
(xMaybe, yMaybe) match {
case (Some(x), Some(y)) => "x and y are there"
case _ => "x and/or y were None"
}
Upvotes: 13