Implicit double dispatch revisited

8 02 2008

In my first post on this topic I showed how generic types can be treated polymorphically in Scala. I employed Scala’s implicits and view bounds to emulate overloading of methods with arguments having the same type erasure.

def foo[T <% Dispatcher[T]](x: T) {
  x.dispatch(new Switch {
    def case_(x: String) {
      println("Got a string " + x)                
    }
    def case_(x: int) {
      println("Got a int " + x)
    }
  })
}

Method foo can be called for any type T for which a conversion to the type Dispatcher[T] is in scope. Dispatcher[T] has a dispatch method for the type T. The method takes a Switch instance and calls its case_ method passing along the dispatched value. See Implicit double dispatch for details.

trait Switch {
  def case_(s: String)
  def case_(n: int)
}

There are two things I don’t like. First, when calling foo with a list argument the list is not passed on as a whole to the respective case_ method. Rather is the case_ method called once for every element of the list iteratively. Second, the foo method is also callable with arguments of type String and type int, not only for Lists of these types.

I generalized my initial code to address these issues. The revisited code is again available on my code page.

To address the first issue I simplified the implementation of foo to

def foo[T <% Dispatcher[T]](x: T) = x.dispatch()

The signature is as before and allows foo to be called for all types T which are implicitly convertible to Dispatcher[T]. The implementation is much simpler however. It still calls the dispatch method of its Dispatcher[T] argument but this time without an argument.

abstract class Dispatcher[T](value: T) {
  def dispatch()
  def dispatch(l: List[T])
}

Again Dispatcher[T] is responsible for handling values of type T. But now there is an additional dispatch method for handling lists of values of type T. The implicit conversion methods create specialized Dispatcher[T] instances for concretes types.

implicit def string2Disp(value: String) = new Dispatcher(value){
  def dispatch() = println("String: " + value)
  def dispatch(l: List[String]) =  println("List[String]: " + l)
}

The above code is for converting a String to a Dispatcher[String]. (Converting an int to a Dispatcher[int] works analogous). The method returns a new instance of type Dispatcher[String] whose dispatch methods outputs the original value. For the first method this is the value originally passed to string2Disp. For the second method the value is a list of strings passed as argument. To see how this second method is called and where that list argument comes from let’s take a look at how lists are converted to their respective Dispatcher objects.

implicit def list2Disp[T](value: List[T])(implicit x2Disp: T => Dispatcher[T]) = new Dispatcher(value) {
  def dispatch() = x2Disp(null.asInstanceOf[T]).dispatch(value)
  def dispatch(l: List[List[T]]) = println("List[List[_]]: " + l)        
}

Say a List[String] is to be converted to Dispatcher[List[String]]. The Scala compiler looks for an implicit argument to be passed for the x2Disp parameter. It finds the string2Disp method and passes it for the x2Disp parameter. Now there is a trick in the implementation of the first dispatch method: a call to x2Disp returns an instance of Dispatcher[String] which is subsequently used to call dispatch on passing along the original list of strings. The argument passed to x2Disp does not matter since any instance of Dispatcher[String] will do. So we simply pass null casted to the right type T.
The second dispatch method which takes a list of lists is similar to the dispatch method for lists in the case of Dispatcher[String] from above. The dispatch mechanism thus works for arguments of type List[List[List[...]]] up to an arbitrary depth (at least in theory).

To address the second issues, all that has to be done is to further restrict the applicable types for the foo method. Such a restriction should only allow lists to be passed as arguments, not individual values. I did not succeed to express such a constraint as an upper bound along the lines of

def foo[T <% Dispatcher[T] <: List[T]](x: T) = x.dispatch()

or similarly. However using existential types the same effect can be achieved:

def foo[T <% Dispatcher[T] forSome { type T <: List[_] }](x: T) = x.dispatch()

This effectively limits type T further to lists convertible to Dispatcher[T]. With this signature in place the following calls to foo are not legal anymore

foo("A String")
foo(42)

while all of the following still works as expected:

val ints: List[int] = Nil
foo(ints)     // empty list of ints
val strings: List[String] = Nil
foo(strings)  // empty list of strings

// Simple lists
foo("one"::"two"::"three"::Nil)
foo(1::2::3::Nil)

// List of lists
foo((1::2::3::Nil)::(4::5::6::Nil)::Nil)
foo(("one"::"two"::Nil)::("three"::"four"::Nil)::Nil)

Finally one may ask: where is the double dispatch? Well, it is hidden inside the implicit argument passed to list2Disp. The first dispatch is explicitly done by the foo method. If we deal with a list, the second dispatch is done on the object returned by the call to the x2Disp argument which was implicitly passed on the first dispatch.

About these ads

Actions

Information

One response

13 08 2008
Puzzle: the depth of a type (solution) « Michid’s Weblog

[...] is the same technique as discussed in my articles on implicit double dispatch. The function depth is applicable to any argument of type T which is implicitly convertible to type [...]

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




Follow

Get every new post delivered to your Inbox.

%d bloggers like this: