Scala how to avoid var during some override condition

Viewed 333

Functional programming style of coding guideline says we should not use null or var in Scala for better functional programming code.

I want to perform some operation like below

    var a = 2
    if(condition==true){
     a = a * 3 /*someOperation*/
    }

   if(condition2==true){
     a = a * 6 /*someOperation*/
    }

   if(condition3==true){
     a = a * 8 /*someOperation*/
    }
    val b = a * 2/*someOperation*/

So now how to avoid var in this condition and replace it with val?

7 Answers

The simplest way to avoid var with multiple conditions is to use temporary values

val a1 = 2
val a2 = if (condition)  a1*3 else a1
val a3 = if (condition2) a2*6 else a2
val a  = if (condition3) a3*8 else a3

val b = a * 2/*someOperation*/

In the real code you would give a1, a2, and a3 meaningful names to describe the result of each stage of processing.

If you are bothered about having these extra values in scope, put it in a block:

val a = {
  val a1 = 2
  val a2 = if (condition)  a1*3 else a1
  val a3 = if (condition2) a2*6 else a2
  if (condition3) a3*8 else a3
}

Update

If you want a more functional approach, collect the conditions and modifications together and apply them in turn, like this:

val mods: List[(Boolean, Int=>Int)] = List(
  (condition,  _*3),
  (condition2, _*6),
  (condition3, _*8)
)

val a = mods.foldLeft(2){ case (a,(cond, mod)) => if (cond) mod(a) else a }

This is really only appropriate when either the conditions or the modifications are more complex, and keeping them together makes the code clearer.

val a = 2 * (if (condition) 3 else 1)
val b = 2 * a

Or, perhaps...

val a = 2
val b = 2 * (if (condition) a*3 else a)

It depends on if/how a is used after these operations.

I think you might have oversimplified your example, because we know the value of a when writing the code so you could just write it out like this:

val a = if (condition) 2 else 6
val b = a * 2

Assuming your real operation is more complex and can't be precalculated like that, then you might find a pattern match like this is a nicer way to do it:

val a = (condition, 2) match {
  case (true, z) => 
    z * 3
  case (false, z) => 
    z
}
val b = a * 2

You can try the following approach:

type Modification = Int => Int
type ModificationNo = Int
type Conditions = Map[ModificationNo, Boolean]

val modifications: List[(Modification, ModificationNo)] = 
    List[Modification](
        a => a * 3, 
        a => a * 6, 
        a => a * 8
    ).zipWithIndex

def applyModifications(initial: Int, conditions: Conditions): Int =
  modifications.foldLeft[Int](initial) {
    case (currentA, (modificationFunc, modificationNo)) =>
      if (conditions(modificationNo)) modificationFunc(currentA)
      else currentA
  }

val a: Int = applyModifications(initial = 2, 
  conditions = Map(0 -> true, 1 -> false, 2 -> true))

It may look complicated but this approach allows additional flexibility if number of conditions is big enough. Now when you have to add more conditions, you don't need to write new if-statements and further reassignments to var. Just add a modification function to an existing list of

there is no 1 perfect solution.
sometimes it is ok to use var if it simplifies the code and limited in scope of a single function.

that being said, this is how I would do it in functional way:

val op1: Int => Int =
if (condition1) x => x * 3
else identity

val op2: Int => Int =
  if (condition2) x => x * 6
  else identity

val op3: Int => Int =
  if (condition3) x => x * 8
  else identity


val op = op1 andThen op2 andThen op3
// can also be written as 
// val op = Seq(op1, op2, op3).reduceLeft(_ andThen _)

val a = 2
val b = op(a) * 2

The easiest way it to wrap your variable into a monad, so that you .map over it. The simplest monad is an Option, so you could write:

val result = Option(a).map { 
  case a if condition => a*2
  case a => a
}.map { 
  case a if condition2 => a*6
  case a => a
}.fold(a) { 
  case a if condition3 => a*8
  case a => a
}

(The last operation is fold instead of map so that you end up with the "raw" value for the result, rather than an option. Equivalently, you could write it as a .map, and then add .getOrElse(a) at the end).

When you have many conditional operations like this, or many use cases where you have to repeat the pattern, it might help to put them into a list, and then traverse that list instead:

def applyConditionals[T](toApply: (() => Boolean, T => T)*) = toApply
  .foldLeft(a) { 
     case (a, (cond, oper)) if cond() => oper(a)
     case (a, _) => a
  }

val result = applyConditionals[Int] (
   (() => condition,  _ * 2),
   (() => condition2, _ * 6),
   (() => condition3, _ * 8)
)

The important point is that FP is a whole new paradigm of programming. Its so fundamentally different that sometimes you can not take an excerpt of imperative code and try to convert it to functional code.

The difference applies not just to code but to the way of thinking towards solving a problem. Functional programming requires you to think in terms of chained mathematical computation which are more or less independent of each other (which means that each of these mathematical computation should not be changing anything outside of its own environment).

Functional programming totally avoids mutation of state. So, if your solution has a requirement to have a variable x which has a value 10 at one point and other value 100 at some other point... then your solution is not functional. And you can not write function code for a solution which is not functional.

Now, if you look at your case (assuming you do not actually need a to be 2 and then change to 6 after sometime) and try to convert it into chain of independent mathematical computation, then the simplest one is following,

val a = if (condition) 2 else 6
val b = a * 2
Related