Idiomatic Scala for applying functions in a chain if Option(s) are defined

2.1k Views Asked by At

Is there a pre-existing / Scala-idiomatic / better way of accomplishing this?

def sum(x: Int, y: Int) = x + y

var x = 10
x = applyOrBypass(target=x, optValueToApply=Some(22), sum)
x = applyOrBypass(target=x, optValueToApply=None, sum)
println(x) // will be 32

My applyOrBypass could be defined like this:

def applyOrBypass[A, B](target: A, optValueToApply: Option[B], func: (A, B) => A) = {
  optValueToApply map { valueToApply =>
    func(target, valueToApply)
  } getOrElse {
    target
  }
}

Basically I want to apply operations depending on wether certain Option values are defined or not. If they are not, I should get the pre-existing value. Ideally I would like to chain these operations and not having to use a var.

My intuition tells me that folding or reducing would be involved, but I am not sure how it would work. Or maybe there is another approach with monadic-fors...

Any suggestions / hints appreciated!

4

There are 4 best solutions below

0
Robin Green On

What I would do in a case like this is use partially applied functions and identity:

def applyOrBypass[A, B](optValueToApply: Option[B], func: B => A => A): A => A =
  optValueToApply.map(func).getOrElse(identity)

You would apply it like this:

def sum(x: Int)(y: Int) = x + y

var x = 10
x = applyOrBypass(optValueToApply=Some(22), sum)(x)
x = applyOrBypass(optValueToApply=None, sum)(x)
println(x)
0
amnn On

Scala has a way to do this with for comprehensions (The syntax is similar to haskell's do notation if you are familiar with it):

(for( v <- optValueToApply ) 
  yield func(target, v)).getOrElse(target)

Of course, this is more useful if you have several variables that you want to check the existence of:

(for( v1 <- optV1
    ; v2 <- optV2
    ; v3 <- optV3
    ) yield func(target, v1, v2, v3)).getOrElse(target)

If you are trying to accumulate a value over a list of options, then I would recommend a fold, so your optional sum would look like this:

val vs = List(Some(1), None, None, Some(2), Some(3))

(target /: vs) ( (x, v) => x + v.getOrElse(0) )
  // => 6 + target

You can generalise this, under the condition that your operation func has some identity value, identity:

(target /: vs) ( (x, v) => func(x, v.getOrElse(identity)) )

Mathematically speaking this condition is that (func, identity) forms a Monoid. But that's by-the-by. The actual effect is that whenever a None is reached, applying func to it and x will always produce x, (None's are ignored, and Some values are unwrapped and applied as normal), which is what you want.

0
0__ On

Yes, you can use fold. If you have multiple optional operands, there are some useful abstractions in the Scalaz library I believe.

var x = 10
x = Some(22).fold(x)(sum(_, x))
x = None    .fold(x)(sum(_, x))
0
Robin Green On

If you have multiple functions, it can be done with Scalaz.

There are several ways to do it, but here is one of the most concise.

First, add your imports:

import scalaz._, Scalaz._

Then, create your functions (this way isn't worth it if your functions are always the same, but if they are different, it makes sense)

val s = List(Some(22).map((i: Int) => (j: Int) => sum(i,j)),
             None    .map((i: Int) => (j: Int) => multiply(i,j)))

Finally, apply them all:

(s.flatten.foldMap(Endo(_)))(x)