Functional Chain of Responsibility pattern

Hi everyone!

If you are familiar with any OOP language you might have been used the Chain of Responsibility pattern at least once. Today’s topic is about solving the same problem but in a functional way. Let’s see the alternatives we have.

OOP Style

Because it’s possible to write OOP and imperative code in Scala one can rewrite this pattern the same way you would do it in Java. This is an example I found on Gist. Obviously, you shouldn’t do it this way!

Partial Functions

In one of the posts of the amazing series The Neophyte’s Guide to Scala by Daniel Westheide he mentions the alternative to the Chain of Responsibility pattern by chaining partial functions using the orElse method. And following this alternative here’s a good example I found in the blog post Design Patterns in Scala by Pavel Fatin.

This is the simplest way to solve it. However one of the cons is that you always need to specify a default partial function in case neither of the functions can be applied.

Let’s say that we want to apply only one of different rules and when we find the right one, we apply it and finish the flow by returning a value. This is how we would do it:

case class DemoState(number: Int)

type PFRule = PartialFunction[DemoState, Option[String]]

private def numberRule(f: Int => Boolean, result: String): PFRule = {
  case DemoState(n) if f(n) => Option(result)
}

val GreaterThanFiveRule = numberRule(_ > 5, "Greater than five")
val LessThanFiveRule    = numberRule(_ < 5, "Less than five")
val EqualsFiveRule      = numberRule(_ == 5, "Equals five")

val NumberRules = GreaterThanFiveRule orElse LessThanFiveRule orElse EqualsFiveRule

NumberRules(5) // It will return "Equals five"
NumberRules(1) // It will return "Less than five"
NumberRules(7) // It will return "Greater than five"

We can combine any PartialFunction of the same type using orElse. The order is very important. If you have rules than can be applied in different cases, the order will define the priority.

Tail Recursive technique

I’ve found one interesting solution using a tail-recursive approach and the rules defined as Either in this post. Let’s say that we have defined the same number rules in this way using Scalaz disjunction:

import scalaz._, Scalaz._

trait Rule[A, B] {
  def handle(request: B): A \/ B
}

type FiveRule = Rule[String, DemoState]

case object GreaterThanFiveRule extends FiveRule {
  override def handle(state: DemoState): String \/ DemoState =
    if (state.number > 5) "Greater than five".left
    else state.right
}

case object LessThanFiveRule extends FiveRule {
  override def handle(state: DemoState): String \/ DemoState =
    if (state.number < 5) "Less than five".left
    else state.right
}

case object EqualsFiveRule extends FiveRule {
  override def handle(state: DemoState): String \/ DemoState =
    if (state.number == 5) "Equals five".left
    else state.right
}

val NumberRules = List(GreaterThanFiveRule, LessThanFiveRule, EqualsFiveRule) 

And the tail-recursive technique to apply only one of the rules:

implicit class RuleListHandler[A, B](list: List[Rule[A, B]]) {
  def applyOnlyOneTailRec(request: B): A \/ B = {
    @tailrec
    def loop(req: B, rules: List[Rule[A, B]]): A \/ B = {
      if (rules.isEmpty) req.right
      else rules.head.handle(req) match {
        case -\/(value)   => value.left
        case \/-(handler) => loop(req, rules.tail)
      }
    }
    loop(request, list)
  }
}

Then we can apply only one rule by calling this function:

// This will return -\/(Equals five)
NumberRules applyOnlyOneTailRec (_.handle(DemoState(5)))

Applicative Functor Traverse

If your code base is mostly Scalaz, another option to apply the same logic is to use the Applicative method traverse that is defined this way:

trait Traverse[F[_]] extends Functor[F] with Foldable[F] { self =>
  def traverseImpl[G[_]:Applicative,A,B](fa: F[A])(f: A => G[B]): G[F[B]]
}

If you’ve ever used Scala Future you might have used both sequence and/or traverse methods:

val list: List[Future[Int]] = List(Future(1), Future(2))
val x: Future[List[Int]] = Future.sequence(list) map (l => l map (n => n+1))
val y: Future[List[Int]] = Future.traverse(list)(l => l map (n => n+1))

Traverse is the same to map over an Applicative and then apply sequence but with a better performance because it doesn’t need the additional list to copy the values before apply map. In the case of Future is just sequence and then map. In Scalaz anything can be traversable if there’s evidence that it’s an Applicative Functor Foldable as defined in The Essence of the Iterator pattern paper. And more about the implementation in Scalaz here.

The tail-recursive technique is a naive solution that we can replace by calling traverse to apply only one of the rules because it’s going to fail-fast:

// This will return -\/(Equals five) as in the tail-recursive solution
NumberRules traverseU (_.handle(DemoState(5)))

The problem with working with the monadic fail-fast technique and disjunction is that we’ll have the expected value in the left side. One thing that we can probably do is to invoke swap once we get the value to flip sides between left and right. I defined an implicit function to do this for any given List of Rules:

implicit class RuleListHandler[A, B](list: List[Rule[A, B]]) {
  def applyOnlyOneTraverse(request: B): List[B] \/ A = {
    list traverseU (_.handle(request)) swap
  }
}

// This will return \/-(Greater than five)
NumberRules applyOnlyOneTraverse DemoState(8)

In Scalaz you will find the function traverse and traverseU (the same with sequence). The difference is that traverseU can derive the type of the Applicative by using unapply whereas for traverse you need to specify the type.

As always, an example project showing different combinations can be found on GitHub.

Until the next post!
Gabriel.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s