2012-12-28

Chain of Responsibility in Scala

Scala implementations of OOD behavior pattern: Chain of Responsibility.

Motivation:
    Chain of Responsibility pattern defines how to decouple senders and receivers, but with different trade-offs (passes a sender request along with chain of potential receivers).
    Show Scala possibilities in different implementation styles: from pure functional to object oriented with Scala features.


Implementation:

  • Variant N1 - pure functional style:

          Will use function A => Either[A, B] for chain items: 
              - Left[A]      for process by next handler.
              - Right[B]    for final proceeded result.

          Define helper class Chain[A,B] to define combination function +>.
          Create companion object Chain for defined implicit conversion function from  A => Either[A, B] to Chain[A,B].

Code:
package my.experiments
package logic

class Chain[A, B](f: A => Either[A, B]) {
  def +>(next: => (A => Either[A, B])): A => Either[A, B] =
    f(_).left.flatMap(next) // call next handler if not yet processed
}

object Chain {
  implicit def func2Chain[A, B](f: A => Either[A, B]) = new Chain[A, B](f)
}


Use Case:
        Typical example: "ATM uses the Chain of Responsibility for money giving mechanism."

Code:
package my.experiments
import logic._

case class CoinCount(coinValue: Int, coinCount: Int)
case class CoinState(currentAmount: Int, acc: List[CoinCount]) // coints state transfer object

object Main {
  def main(args: Array[String]) = {
    import Chain._  // import implicit conversion function
    
    // function for setup coin value in coins calculate function
    val func =
      (coin: Int) =>
      (obj: CoinState) =>
        if(obj.current % coin == 0) Right(CoinCount(coin, obj.current / coin) :: obj.acc)
        else Left(CoinState(obj.current % coin, CoinCount(coin, obj.current / coin) :: obj.acc))

    val chainFunc = func(5) +> func(3) +> func(2) // define Chain of Responsibility function

    println {
        chainFunc(CoinState(12, Nil))  // handle request by chain
    }
  }
}

  • Variant N2 - Scala based mixing of Object Oriented & Functional Style:

          Define trait Chain with abstract function handle(obj: A) : Either[A,B] for items chaining(where A for processing message type, B is final processed type):
              - Left[A]      for process by next handler.
              - Right[B]    for final proceeded result.

          To define the chain List[Chain[A,B]] will be used and companion object will be defined with implicit conversion function from list to Chain with defined handle method (composite pattern as is).

Code:
package my.experiments
package logic

import annotation.tailrec

trait Chain[A,B] {
  def handle(obj: A) : Either[A,B]
}

object Chain {
  implicit def list2Chain[A,B](list: List[Chain[A,B]]) = new Chain[A,B] {
    override def handle(obj: A): Either[A,B] = fold(obj, list)

    @tailrec                                          // check that method will be optimised
    private def fold(obj: A, handlers: List[Chain[A,B]]): Either[A,B] = {
      if(handlers.isEmpty) Left(obj)                                   
      else handlers.head.handle(obj) match {
        case msg : Right[A,B] => msg                 // return result if message is handled
        case Left(msg) => fold(msg, handlers.tail)   // or transfer message to next handler
      }
    }
  }
}

Using sample:
        The same example like above: "ATM uses the Chain of Responsibility for money giving mechanism."

Code:
package my.experiments

import logic

case class CoinCount(coinValue: Int, coinCount: Int)
case class CoinState(current: Int, acc: List[CoinCount])

case class CountChain (coin: Int) extends Chain[CoinState, List[CoinCount]] {
  override def handle(obj: CoinState) = {
    if(obj.current % coin == 0) Right(CoinCount(coin, obj.current / coin) :: obj.acc)
    else Left(CoinState(obj.current % coin, CoinCount(coin, obj.current / coin) :: obj.acc))
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    // define Chain of Responsibility classes list
    // convert it to Chain "composite"
    // and call handle function with defined parameters
    val ret = CountChain(5) :: CountChain(3) :: CountChain(1) :: Nil handle CoinState(14, Nil)
    println(ret)
  }
}


  • Variant N...


        There are many interesting implementations of Chain of Responsibility pattern in Scala can be constructed. For instance take implementation based on Trait mixing where abstract function overridden (abstract override def) by another abstract function and call super implementation until message has been processed. And chain can be defined by mixing of traits.
        This method has some limitations regarding to adding parametrization for traits or adding the same with different options.

        And also pure Object Oriented implementation can be used because Scala is open for mixing functional and object oriented stiles.


Note:
        In current post was used few Scala features:
  • High Order Function.
  • By-name parameters.
  • Either monad.
  • Companion object.
  • Implicit conversions.

        High Order Function is functions that apply policy to other function in order to produce new function. It is mean definition of function what takes a function as argument, or return a function as a result.


        By-name paremeter  is argument of function what is not eveluated at the point of function application, but instead is evaluated at each use whithin the function. Syntax: paramName :=> paramType.

        High oreder function & by-name parameter example in code abow looks:
def +>(next: => (A => Either[A, B])): A => Either[A, B] = ...

        Either[A,B] (Scala docs here) will used like State monad which allows to attach state information to a calculation. Given any value type (A, B) the corresponding type in the state monad (Left[A], Right[B]) which defines result of calculation.
        Monadic features like projection and map used for next message processing based on current state of result type.

        Implicit conversion function is a function with a single parameter (with the implicit keyword) which automatically applied to convert values from one type to another. It can have any name and it don't needed to call explicitly.
     
Rules of implicit import:


  • Implicit function in companion object of the source or target type.
  • Implicit function that are in scope as a single identifier.