Tristan
Tristan

Reputation: 6906

Structural Type Dispatch in Scala

I'm trying to get a better grasp of structural type dispatch. For instance, assume I have an iterable object with a summary method that computes the mean. So o.summary() gives the mean value of the list. I might like to use structural type dispatch to enable summary(o).

  1. Is there a set of best practices regarding o.summary() vs. summary(o)?
  2. How does scala resolve summary(o) if I have a method summary(o: ObjectType) and summary(o: { def summary: Double})
  3. How does structural type dispatch differ from multimethods or generic functions?

Michael Galpin gives the following discription about structural type dispatch:


Structural types are Scala’s version of “responds-to” style programming as seen in many dynamic languages. So like

def sayName ( x : { def name:String }){
    println(x.name)
}

Then any object with a method called name that takes no parameters and returns a string, can be passed to sayName:

case class Person(name:String)
val dean = Person("Dean")
sayName(dean) // Dean

Upvotes: 4

Views: 1762

Answers (3)

cayhorstmann
cayhorstmann

Reputation: 3361

I think you asked what Scala does with a call on a structural type. It uses reflection. For example, consider

def repeat(x: { def quack(): Unit }, n: Int) {
   for (i <- 1 to n) x.quack()
}

The call x.quack() is compiled into a lookup of the quack method, and then a call, both using Java reflection. (You can verify that by looking at the byte codes with javap. Look for a class with a funny name like Example$$anonfun$repeat$1.)

If you think about it, it's not surprising. There is no way of making a regular method call because there is no common interface or superclass.

So, the other respondents were absolutely right that you don't want to do this unless you must. The cost is very high.

Upvotes: 0

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297155

Structural data types aren't really all that useful. That's not to say they are useless, but they definitely a niche thing.

For instance, you might want to write a generic test case for the "size" of something. You might do it like this:

def hasSize(o: { def size: Int }, s: Int): Boolean = {
  o.size == s
}

This can then be used with any object that implements the "size" method, no matter its class hierarchy.

Now, they are NOT structural type dispatches. They are NOT related to dispatching, but to type definition.

And Scala is an object oriented language always. You must call methods on objects. Function calls are actually "apply" method calls. Things like "println" are just members of objects imported into scope.

Upvotes: 3

Flaviu Cipcigan
Flaviu Cipcigan

Reputation: 7243

-- 1. In your example, I wouldn't use the summary(o) version, as this is not a very object oriented style of programming. When calling o.summary (you could drop the brackets as it has no side-effects), you are asking for the summary property of o. When calling summary(o), you are passing o to a method that calculates the summary of o. I believe that the first approach is nicer :).

I haven't used structural type dispatch much, but I assume that it is best suited (in a large system) for the case where you would have to write an interface just because one method wants a type that has some method defined. Sometimes creating that interface and forcing the clients to implement it can be awkward. Sometimes you want to use a client defined in another API which conforms to your interface but doesn't explicitly implement it. So, in my opinion, structural type dispatch serves as a nice way to make the adapter pattern implicitly (saves on boilerplate, yay!).

-- 2. Apparently if you call summary(o) and o is of ObjectType, summary(o: ObjectType) gets called (which does make sense). If you call summary(bar), in which bar is not of ObjectType, two things can happen. The call compiles if bar has the method summary() of the right signature and name or otherwise, the call doesn't compile.

Example:

scala> case class ObjectType(summary: Double)
defined class ObjectType

scala> val o = ObjectType(1.2)
o: ObjectType = ObjectType(1.2)

scala> object Test {
     | def summary(o: ObjectType)  { println("1") }
     | def summary(o: { def summary: Double}) { println("2")}
     | }
defined module Test

scala> Test.summary(o)
1

Unfortunately, something like the following does not compile due to type erasure:

scala> object Test{
     | def foo(a: {def a: Int}) { println("A") }
     | def foo(b: {def b: Int}) { println("B") }
     | }
:6: error: double definition:
method foo:(AnyRef{def b(): Int})Unit and
method foo:(AnyRef{def a(): Int})Unit at line 5
have same type after erasure: (java.lang.Object)Unit
       def foo(b: {def b: Int}) { println("B") }

-- 3. In a sense, structural type dispatch is more dynamic than generic methods, and also serves a different purpose. In a generic method you can say either: a. I want something of any type; b. I want something of a type that is a subtype of A; c. I'll take something that is a supertype of B; d. I'll take something that has an implicit conversion to type C. All of these are much stricter than just "I want a type that has the method foo with the right signature". Also, structural type dispatch does use reflection, as they are implemented by type erasure.

I don't know much about multimethods, but looking at the wikipedia article, it seems that multimethods in Scala can be achieved using pattern matching. Example:

def collide(a: Collider, b: Collider) = (a, b) match {
    case (asteroid: Asteroid, spaceship: Spaceship) => // ...
    case (asteroid1: Asteroid, asteroid2: Asteroid) => // ...
... 

Again, you could use structural type dispatch - def collide(a: {def processCollision()}), but that depends on a design decision (and I would create an interface in this example).

-- Flaviu Cipcigan

Upvotes: 3

Related Questions