2013-04-01

Chain call style with if-else injection in Scala

10:09 PM Posted by Yuriy Polyulya , ,
If-else injection in functions chain call style design.

Motivation:
    To make functions call chain continuous a conditional operator like: if or if-else sometimes needed. As far as Scala is not pure functional language and some functions in chain can be changed according to some local conditions or variables. So a goal of if-else operator injection is to make chain continuous without temporal variables usage.    It is applicable for Immutable Objects where forming function call chain is natural by design.

Immutable object example.

Code:
package my.experiments

case class ImmInt(private val _v: Int) {
   def value = _v
   def inc(x: Int) = ImmInt(_v + x)
   def dec(x: Int) = ImmInt(_v - x)
   def mul(x: Int) = ImmInt(_v * x)
   def div(x: Int) = ImmInt(_v / x)
}

and ImmInt object using example.

Code:
   println(ImmInt(10).inc(5).dec(2).div(5).value)

in real development we can have a situation when dec(2) will be dependent from some local variable for example.

Code:
   var localVariable = "yes"
   val a1 = ImmInt(10).inc(5) // temp variable for next if conditions
   val a2 = {
         if(localVariable != "yes") a1.dec(2)
         else a1.inc(2)
      }.div(5).value

   println(a2)

but better to have something like code below.

Code:
   println(ImmInt(10).inc(5).
      $if(localVariable != "yes") { _.dec(2) }. 
      $else{ _.inc(2) }.
      div(5).value)


Solution:
    For make if-else injection possible, will define two helper classes. First one (ConditionalApplicative, it will be generic) with $if function what return second one (ElseApplicative, it can be nested class) with $else function. And two implicit conversion functions in scope of companion object:
          - from Any to ConditionalApplicative[T]
          - from ElseApplicative to substituted T

Code:
package my.experiments
package logic

sealed class ConditionalApplicative[T] private (val value: T) { // if condition class wrapper
   class ElseApplicative(value: T, elseCondition: Boolean) extends ConditionalApplicative[T](value) {
   // else condition class wrapper extends ConditionalApplicative to avoid double wrapping 
   // in case: $if(condition1) { .. }. $if(condition2) { .. }
      def $else(f: T => T): T = if(elseCondition) f(value) else value
   }

   // if method for external scope condition
   def $if(condition: => Boolean)(f: T => T): ElseApplicative = 
      if(condition) new ElseApplicative(f(value), false) 
      else new ElseApplicative(value, true)

   // if method for internal scope condition
   def $if(condition: T => Boolean) (f: T => T): ElseApplicative = 
      if(condition(value)) new ElseApplicative(f(value), false) 
      else new ElseApplicative(value, true) 
}

object ConditionalApplicative { // Companion object for using ConditionalApplicative[T] generic
   implicit def lift2ConditionalApplicative[T](any: T): ConditionalApplicative[T] = 
      new ConditionalApplicative(any)

   implicit def else2T[T](els: ConditionalApplicative[T]#ElseApplicative): T = 
      els.value
}  


Method of using is simple, just include companion object in using scope.

Code:
package my.experiments

case class ImmInt(private val _v: Int) {
   def value = _v
   def inc(x: Int) = ImmInt(_v + x)
   def dec(x: Int) = ImmInt(_v - x)
   def mul(x: Int) = ImmInt(_v * x)
   def div(x: Int) = ImmInt(_v / x)
}

object Main {
   def main(args: Array[String]) = {
      import logic.ConditionalApplicative._

      println(
         ImmInt(10).inc(5).
         $if(localVariable != "yes") { _.dec(2) }. // checking localVariable
         $else { _.inc(2) }.                       // else condition (alternative action)
         div(5).value
      )
   }
}

else sentence can be skipped in case if it is not needed.

Example code:
   println(
      ImmInt(10).inc(5).
      $if(localVariable != "yes") { _.dec(2) }.
      div(5).value
   )

And after all I have found some interesting effects.  For instance simple Int type can be applied  expression like following.

Code:
object Main {
   def main(args: Array[String]) = {
      import ConditionalApplicative._

      var localVariable = "yes"
      var r = 5
      var r1 = // temp variable for intermediate result
         if(localVariable != "yes") 100 + r
         else 100 - r
      println(r1)
   }
}

Can be easy replaced by function call chain.

Code:
object Main {
   def main(args: Array[String]) = {
      import ConditionalApplicative._

      var localVariable = "yes"
      val r = (5).
         $if(localVariable != "yes") { 100+ }.
         $else { 100- }
      println(r)
   }
}

Also internal scope condition can be used for if-else operator. Example code below.

Code:
   val r = (5).
      $if(_ > 100) { 100+ }.
      $else { 100- }

   println(r)
Note:
    In this post few Scala features were used:
  • High Order Function.
  • By-Name Parameter.
  • Companion Object.
  • Implicit Conversions.
Nothing special new.