Why traits and not just data?

I started learning Scala coming from the perspective of functional programming. I really want to “get” traits/mixins, but I’m having trouble seeing any reason to use inheritance/super heavily to achieve “stackability” when weighed against the benefit of a more data-driven approach that just uses functions as data.

In the chapter on traits in the excellent Artima book, Programming in Scala, their example to demonstrate the heavy use of traits is the idea of a modifiable queue, which performs certain stackable modifications on its inputs/outputs. The authors urge you to consider traits in any situation where you are doing a lot of “stackable modifications”.

I’m not sure why you wouldn’t just do this using lists of functions. Instead of relying on super and linearization to apply modifiers in the correct order, just treat the concept of a “modifier” as a piece of data and manipulate it as you would any other data. What’s nice about this is that these data-driven “mixins” can be added at runtime, removed, and re-ordered.

I’m not claiming traits are useless or anything – there are definitely benefits to first-class syntax, and I’m pretty sure I’m missing something more than that. But it seems that most of the time, if you want to organize code as a bunch of stackable modifications, function-valued data is a simpler and more flexible way to go than trying to ponder your way through a bunch of multiple inheritance.

Their example:

import collection.mutable.ArrayBuffer

object MainModule {

  abstract class IntQueue {
    def get(): Int
    def put(x: Int)
  }

  class BasicIntQueue extends IntQueue {
    private val buf = new ArrayBuffer[Int]
    def get() = buf.remove(0)
    def put(x: Int) { buf += x}
  }

  trait Doubling extends IntQueue {
    abstract override def put(x: Int) { super.put(2 * x)}
  }

  trait Incrementing extends IntQueue {
    abstract override def put(x: Int) { super.put(x + 1)}
  }

  def main(args: Array[String]) {

    val q1 = new BasicIntQueue with Doubling with Incrementing

    q1 put 10
    q1 put 20

    println(q1.get())
    println(q1.get())

    val q2 = new BasicIntQueue with Incrementing with Doubling

    q2 put 10
    q2 put 20

    println(q2.get())
    println(q2.get())

  }

}

My re-writing:

import collection.mutable.ArrayBuffer

object MainModule2 {

  abstract class Queue[T] {
    def put(item: T)
    def get(): T
  }

  trait BasicIntQueue extends Queue[Int] {
    private val buf = new ArrayBuffer[Int]
    def get() = buf.remove(0)
    def put(x: Int) { buf += x }
  }

  case class ModifiableQueue(
    getModifiers: List[Int=>Int],
    putModifiers: List[Int=>Int]) extends BasicIntQueue {

    private def compose[A](f: A => A, g: A => A): A => A =
      { x => f(g(x)) }

    override def get() = (getModifiers reduce compose[Int])(super.get())
    override def put(item: Int) {
      super.put((putModifiers reduce compose[Int])(item))
    }

    def modify(qm: QueueModifier) =
      ModifiableQueue(
        this.getModifiers ++ List(qm.getModifier),
        this.putModifiers ++ List(qm.putModifier))
  }

  case class QueueModifier(
    getModifier: Int => Int = { x => x },
    putModifier: Int => Int = { x => x })

  val doubling = QueueModifier(putModifier = { 2 * _ })

  val incrementing = QueueModifier(putModifier = { 1 + _ })

  def queue = new ModifiableQueue(Nil, Nil)

  def main(args: Array[String]) {

    // just like mixins:
    val q1 = queue modify doubling modify incrementing

    q1 put 10
    q1 put 20

    println("increment, then double:")

    println(q1.get())
    println(q1.get())

    val q2 = queue modify incrementing modify doubling

    q2 put 10
    q2 put 20

    println("double, then increment:")

    println(q2.get())
    println(q2.get())

    // but now "traits" are just first-class data...
    // way better than mixins because it can be done at runtime,
    // composably, higher-order, etc:

    val q3 = (List(incrementing, doubling) foldLeft queue) { _ modify _ }

    q3 put 10
    q3 put 20

    println("folded in modifiers:")

    println(q3.get())
    println(q3.get())

    // they can even be removed, stacked N times, etc:

    val q4 = q3.copy(putModifiers = q3.putModifiers.tail)

    q4 put 10
    q4 put 20

    println("removed last modifier:")

    println(q4.get())
    println(q4.get())

    def modifyN(q: ModifiableQueue, qm: QueueModifier, n: Int) =
      (1 to n map { _ => qm } foldLeft q) { _ modify _ }

    val q5 = modifyN(queue, doubling, 5)

    q5 put 10
    q5 put 20

    println("doubled 5 times:")

    println(q5.get())
    println(q5.get())

  }

}

This is a very simple example, and has the downside that a new “modifier” list must be added for each method we which to modify. I’m going to think this through some more and see if I can’t figure out a more generic approach that uses reflection, traits, dictionaries, or any of the above. One approach could be to define a generic trait that represents the “wrapping” of one method, and mix that in for each method you wish to stackably modify.

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

%d bloggers like this: