Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 360
Sebastian N. Avatar asked Dec 29 '13 11:12

Sebastian N.


3 Answers

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.

like image 82
amnn Avatar answered Nov 08 '22 06:11

amnn


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)
like image 23
Robin Green Avatar answered Nov 08 '22 04:11

Robin Green


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))
like image 30
0__ Avatar answered Nov 08 '22 06:11

0__