Wai Yip Tung Wai Yip Tung - 21 days ago 7
Scala Question

Idiomatic way to perform a conditional transformation

I have an object

x
. I am going to perform a series of transformation on it. This can easily be expressed as

x.transformA.transformB.transformC


However, before each transformation, I should also check for a condition. Only if the condition is true should I perform the transformation. I have two way to express this. The first is to define x as a var.

var x = anObject
if (x.condA) x = x.transformA
if (x.condB) x = x.transformB
if (x.condC) x = x.transformC
// final result in x


Alternatively, to avoid var, I define a series of intermediate variables

val x0 = anObject
val x1 = if (x0.condA) x0.transformA else x0
val x2 = if (x1.condB) x1.transformB else x1
val x3 = if (x2.condC) x2.transformC else x2
// final result in x3


This seems to be quite cumbersome, even error prone when I cut and paste the lines. Is there a more idiomatic way to express this?

Answer

It's possible to express a series of conditional transformations as a composition of Functions. That way each transformation can be declared independently, and reused to build new tranformations.

As an example:

val ta: MyClass => MyClass = {case x if x.condA => x.transformA
                              case x => x}
val tb: MyClass => MyClass = {case x if x.condB => x.transformB
                              case x => x}
val tc: MyClass => MyClass = {case x if x.condC => x.transformC
                              case x => x}

Then we can compose those to form our transformation:

val transformation = ta andThen tb andThen tc
val transformedObj =  transformation(x)

We can see above that we are repeating the same pattern over and over again. To avoid that, we could make a function that creates our partial functions, reducing the boilerplate code:

def mkTf[T](cond: T => Boolean, transf: T => T): T => T = {
  case x if cond(x) => transf(x)
  case x => x
}

Then we can rewrite our composition as:

val transformation = mkTf[MyClass](_.condA,_.transformA) andThen
                     mkTf[MyClass](_.condB,_.transformB) andThen
                     mkTf[MyClass](_.condC,_.transformC)