Sunday, May 18, 2008

Choosing between Abstract Type Members and Existentials in Scala

Scala offers abstract type members as a means to abstract over concrete types of components. The internal type of an abstraction can be hidden from the user who can invoke methods on objects without knowing the actual type of the member. Recently I was implementing a Strategy design pattern based abstraction for computing salaries of all types of workers in an organization. I did not design a common base class of all the types of workers in the model and was trying to build up my abstraction hierarchy based on composition of components. Here is what I came up with (simplified for brevity) ..


object SalaryComputation {

  trait Source {
    type E
    val salaried: List[E]
    val salCalculator: (=> Double)
  }

  class Context {
    val payees = new ArrayBuffer[Source]

    def addSource[T](p: List[T], salCalc: (=> Double)) = {
      payees += new Source {
        type E = T
        val salaried = p
        val salCalculator = salCalc
      }
    }
  }

  def compute = {
    val emps = List[Employee](
      //..
    )

    val cemps = List[Contractor](
      //..
    )

    val c = new Context()
    c.addSource(emps, (e: Employee) => (e.monthlyBasic + e.allowance - e.monthlyBasic * 0.3))
    c.addSource(cemps, (e: Contractor) => (e.hourlyRate * 8 * 20) * 0.7)

    c.payees.map(=> p.salaried.map(p.salCalculator(_))).foreach(println)
  }
}



and the workers are modeled as case classes ..


case class Employee(id: Int, name: String, monthlyBasic: Int, allowance: Int)
case class Contractor(id: Int, name: String, hourlyRate: Int)
case class DailyWorker(id: Int, name: String, dailyRate: Int, overtimeRate: Int)



The salary computation part has a separate Source component which abstracts the data source and salary computation strategy for one particular class of worker. I can have multiple classes of workers being fed into the salary computation component through multiple instances of the Source component. And this aggregation is managed as the Context of the computation. Have a look at the compute method that adds up Sources into the context and builds up the collection for total computation of salaries.

In the above implementation, the trait Source abstracts the type of the worker over the collection for which salary will be computed and the salary computation strategy. And both of them are injected when new instances of Source are being created in method addSource(). The client interface to the abstraction (in method compute) looks nice and DRY and it works as per expectation.

But what about Existential Types ?

In one of the recent releases, Scala has introduced existential types, which offers hiding (pack) of concrete types from the user and allows them to manipulate objects using a given name (unpack) and bounds. Keeping aside all type theoretic foundations behind existential types, I found it equally convenient to implement the above model using existential types instead of abstract type members. Here it goes ..


object SalaryComputation {

  case class Source[E](salaried: List[E], salCalculator: (=> Double))

  class Context {
    val payees = new ArrayBuffer[Source[T] forSome { type T }]

    def addSource[T](p: List[T], salCalc: (=> Double)) = {
      payees += new Source[T](p, salCalc)
    }
  }

  def compute = {
    val emps = List[Employee](
      //..
    )

    val cnts = List[Contractor](
      //..
    )

    val c = new Context()
    c.addSource(emps, (e: Employee) => (e.monthlyBasic + e.allowance - e.monthlyBasic * 0.3))
    c.addSource(cnts, (e: Contractor) => (e.hourlyRate * 8 * 20) * 0.7)

    def calc[T](c: Source[T]) = {
      c.salaried.map(c.salCalculator(_))
    }

    c.payees.map(calc(_)).foreach(println(_))
  }
}



The Scala book by Artima mentions that existential types have been introduced in Scala mainly for interoperability between Scala and Java's wild card types and raw types. But as I found above, they offer all benefits of type abstraction as abstract type members. Ignoring the stylistic issues, is there any reason to choose one approach over the other in the above implementations ?

4 comments:

Antony Stubbs said...

My gosh, how do you find time to write so much about Scala? Do you use it a _lot_ at work?

Luc Duponcheel said...

Hi,

the Artima book shows how to use type members to enforce static constraints such as, when animals eat food, cows are only allowed to eat grass (btw: the book also has a very convincing currency example)

do you think you can also elegantly impose such static constraints using existential types?

Luc

Germán said...

Debasish,
See what M. Odersky says in a comment on this blog post:
http://www.drmaciver.com/2008/03/existential-types-in-scala/
Cheers

Unknown said...

@german: Yeah .. I have seen in many places that generally abstract type members are recommended over existentials. But yet to find any convincing arguments for that. The artima book also mentions that existentials are mainly targetted for interoperating with Java generics. However, I have seen some use cases where I think existentials will make a better cut.