davetron5000
davetron5000

Reputation: 24841

for..else for Option types in Scala?

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

Answers (9)

AndreasScheinert
AndreasScheinert

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

Xavier Guihot
Xavier Guihot

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

Emil Ivanov
Emil Ivanov

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

Tony Morris
Tony Morris

Reputation: 3063

The traverse function in Scalaz generalises your problem here. It takes two arguments:

  1. T[F[A]]
  2. 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:

  • val f: X => Option[Y] = ...

and call traverse

  • val r = z traverse f

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

rwallace
rwallace

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

paradigmatic
paradigmatic

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

Tomasz Nurkiewicz
Tomasz Nurkiewicz

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

Dylan
Dylan

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

kassens
kassens

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

Related Questions