This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Best way to do multiple virtual dispatch?

5 replies
Rüdiger Klaehn
Joined: 2009-06-02,
User offline. Last seen 42 years 45 weeks ago.

Hi all,

what is the best way to do dynamic dispatch on more than one
argument?

The situation is as follows: I have two variables a,b which both can
be of type T1,T2,T3,.. and want to execute a binary operation that has
different logic depending on the type of both arguments.

I guess the canonical way to do something like this would be to
pattern match on a pair. But this will be quite slow if the number of
different types I have to handle is large, right?

def sum(arg1:T,arg2:T) = (arg1,arg2) match {
case (a:T1, b:T1) => ...
case (a:T2, b:T1) => ...
case (a:T2, b:T1) => ...
case _ => ...
}

A very fast way would be to use double virtual dispatch, but to do
this I would have to add a lot of boilerplate to each of the concrete
types T1, T2, T3... I would prefer to keep the implementation logic
completely outside the types for some operations.

trait T {
final def +(arg2:T) = arg2.sum1(this)

protected def sum1(arg1:T) : T

protected def sum2(arg2:T1) : T

protected def sum2(arg2:T2) : T
}

trait T1 {
protected def sum1(arg1:T) = arg.sum2t1(this)

protected def sum2(arg2:T1) = // we now know the type of both
ourselves (T1) and the second argument (T1)

...
}

trait T2 {
protected def sum1(arg1:T) = arg.sum2t2(this)

protected def sum2(arg2:T1) = // we now know the type of both
ourselves (T2) and the second argument (T1)

...
}

Any suggestions?

Tony Morris 2
Joined: 2009-03-20,
User offline. Last seen 42 years 45 weeks ago.
Re: Best way to do multiple virtual dispatch?

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hello,
Welcome to Scala, where we have summation abound!

In Scala, we have algebraic data types (ADTs) whereby instead of casing
on the type, we create "case classes" representing the various values
that your type can be. A strong indicator that you want an ADT is when
you say out loud the word "or." For example, you might want "a person or
a building." We may model this as a common type Thing from which we have
two subtypes Person and Building, then when we wish to distinguish them,
we would type-case on them (i.e. is of type Person?). Type-casing is
considered a circumvention of the type system, which in a language like
Scala is strongly discouraged in favour of ADTs.

Instead, we would write an ADT like so:

sealed trait Thing
case class PersonThing(p: Person) extends Thing
case class BuildingThing(b: Building) extends Thing

This way, we needn't use any type-unsafe operations and we also get
syntactic and compositional benefits -- with absolutely no downsides yay!

The sealed keyword indicates that all subtypes of Thing exist in the
source file in which it is declared, so when we inspect (pattern-match)
a value of the type Thing, we can guarantee that it was constructed by
one of the two given case class constructors.

For your use-case you would have three case class constructors, each
accepting one of T1, T2 and T3.

Under an algebraic correspondence, this notion of "disjunction" (this or
that) corresponds to summation (this plus that). This is why these ADTs
are sometimes called sum types.

Hope that helps and welcome to summation!

On 01/19/2012 07:39 PM, rklaehn wrote:
> Hi all,
>
> what is the best way to do dynamic dispatch on more than one
> argument?
>
> The situation is as follows: I have two variables a,b which both can
> be of type T1,T2,T3,.. and want to execute a binary operation that has
> different logic depending on the type of both arguments.
>
> I guess the canonical way to do something like this would be to
> pattern match on a pair. But this will be quite slow if the number of
> different types I have to handle is large, right?
>
> def sum(arg1:T,arg2:T) = (arg1,arg2) match {
> case (a:T1, b:T1) => ...
> case (a:T2, b:T1) => ...
> case (a:T2, b:T1) => ...
> case _ => ...
> }
>
> A very fast way would be to use double virtual dispatch, but to do
> this I would have to add a lot of boilerplate to each of the concrete
> types T1, T2, T3... I would prefer to keep the implementation logic
> completely outside the types for some operations.
>
> trait T {
> final def +(arg2:T) = arg2.sum1(this)
>
> protected def sum1(arg1:T) : T
>
> protected def sum2(arg2:T1) : T
>
> protected def sum2(arg2:T2) : T
> }
>
> trait T1 {
> protected def sum1(arg1:T) = arg.sum2t1(this)
>
> protected def sum2(arg2:T1) = // we now know the type of both
> ourselves (T1) and the second argument (T1)
>
> ...
> }
>
> trait T2 {
> protected def sum1(arg1:T) = arg.sum2t2(this)
>
> protected def sum2(arg2:T1) = // we now know the type of both
> ourselves (T2) and the second argument (T1)
>
> ...
> }
>
> Any suggestions?

Matthew Pocock 3
Joined: 2010-07-30,
User offline. Last seen 42 years 45 weeks ago.
Re: Best way to do multiple virtual dispatch?
Hi rklaehn,
One way to do this is to use a typeclass and implicits. The plumbing looks something like:
trait BinOp[A, B, T] {  def apply(a: A, b: B): T}
case class Addable[A](a: A) {  def +[B, T](b: B)(implicit op: BinOp[A, B, T]): T = op(a, b)}
implicit def asAddable[A](a: A): Addable = Addable(a)


To add an implementation for particular types, provide an implicit implementation like this:
implicit object StringIntIntOp extends BinOp[String, Int, Int] {   def apply(s: String, i: Int): Int = s.length + i}
Matthew
On 19 January 2012 09:39, rklaehn <rklaehn@googlemail.com> wrote:
Hi all,

what is the best way to do dynamic dispatch on more than one
argument?

The situation is as follows: I have two variables a,b which both can
be of type T1,T2,T3,.. and want to execute a binary operation that has
different logic depending on the type of both arguments.

I guess the canonical way to do something like this would be to
pattern match on a pair. But this will be quite slow if the number of
different types I have to handle is large, right?

def sum(arg1:T,arg2:T) = (arg1,arg2)  match {
 case (a:T1, b:T1) => ...
 case (a:T2, b:T1) => ...
 case (a:T2, b:T1) => ...
 case _ => ...
}

A very fast way would be to use double virtual dispatch, but to do
this I would have to add a lot of boilerplate to each of the concrete
types T1, T2, T3... I would prefer to keep the implementation logic
completely outside the types for some operations.

trait T {
 final def +(arg2:T) = arg2.sum1(this)

 protected def sum1(arg1:T) : T

 protected def sum2(arg2:T1) : T

 protected def sum2(arg2:T2) : T
}

trait T1 {
 protected def sum1(arg1:T) = arg.sum2t1(this)

 protected  def sum2(arg2:T1) = // we now know the type of both
ourselves (T1) and the second argument (T1)

 ...
}

trait T2 {
 protected def sum1(arg1:T) = arg.sum2t2(this)

 protected def sum2(arg2:T1) = // we now know the type of both
ourselves (T2) and the second argument (T1)

 ...
}

Any suggestions?



--
Dr Matthew PocockIntegrative Bioinformatics Group, School of Computing Science, Newcastle Universitymailto: turingatemyhamster@gmail.com gchat: turingatemyhamster@gmail.commsn: matthew_pocock@yahoo.co.uk irc.freenode.net: drdozerskype: matthew.pococktel: (0191) 2566550mob: +447535664143
Rüdiger Klaehn
Joined: 2009-06-02,
User offline. Last seen 42 years 45 weeks ago.
Re: Best way to do multiple virtual dispatch?
Won't that only work if the type of the arguments is known at compile time? Implicit resolution is done at compile time, right? So in the example below, if I have
val a:Any = "bla" val b:Any = 3val c:Any = a + b
He will implicitly convert a to an Addable, then look for an implicit BinOp[Any, Any, Any] and fail to find one.
Here is the code with the + operator changed to ~+~ since String already has a + operator:
trait BinOp[A, B, T] {  def apply(a: A, b: B): T}
case class Addable[A](a: A) {  def ~+~[B, T](b: B)(implicit op: BinOp[A, B, T]): T = op(a, b) }
implicit def asAddable[A](a: A): Addable[A] = Addable(a)
implicit object StringIntIntOp extends BinOp[String, Int, Int] {  def apply(s: String, i: Int): Int = s.length + i }
Here is trying it on the repl with types known at compile time:
val a="Bla"val b=1val c=a ~+~ b
defined trait BinOpdefined class AddableasAddable: [A](a: A)Addable[A]defined module StringIntIntOpa: java.lang.String = Blab: Int = 1c: Int = 4
And here is with using Any:
val a2:Any="Bla"val b2:Any=1val c2=a2~+~b2
error: could not find implicit value for parameter op: BinOp[Any,Any,T]
On Fri, Jan 20, 2012 at 4:30 PM, Matthew Pocock <turingatemyhamster@gmail.com> wrote:
Hi rklaehn,
One way to do this is to use a typeclass and implicits. The plumbing looks something like:
trait BinOp[A, B, T] {  def apply(a: A, b: B): T}
case class Addable[A](a: A) {  def +[B, T](b: B)(implicit op: BinOp[A, B, T]): T = op(a, b)}
implicit def asAddable[A](a: A): Addable = Addable(a)


To add an implementation for particular types, provide an implicit implementation like this:
implicit object StringIntIntOp extends BinOp[String, Int, Int] {   def apply(s: String, i: Int): Int = s.length + i}
Matthew
On 19 January 2012 09:39, rklaehn <rklaehn@googlemail.com> wrote:
Hi all,

what is the best way to do dynamic dispatch on more than one
argument?

The situation is as follows: I have two variables a,b which both can
be of type T1,T2,T3,.. and want to execute a binary operation that has
different logic depending on the type of both arguments.

I guess the canonical way to do something like this would be to
pattern match on a pair. But this will be quite slow if the number of
different types I have to handle is large, right?

def sum(arg1:T,arg2:T) = (arg1,arg2)  match {
 case (a:T1, b:T1) => ...
 case (a:T2, b:T1) => ...
 case (a:T2, b:T1) => ...
 case _ => ...
}

A very fast way would be to use double virtual dispatch, but to do
this I would have to add a lot of boilerplate to each of the concrete
types T1, T2, T3... I would prefer to keep the implementation logic
completely outside the types for some operations.

trait T {
 final def +(arg2:T) = arg2.sum1(this)

 protected def sum1(arg1:T) : T

 protected def sum2(arg2:T1) : T

 protected def sum2(arg2:T2) : T
}

trait T1 {
 protected def sum1(arg1:T) = arg.sum2t1(this)

 protected  def sum2(arg2:T1) = // we now know the type of both
ourselves (T1) and the second argument (T1)

 ...
}

trait T2 {
 protected def sum1(arg1:T) = arg.sum2t2(this)

 protected def sum2(arg2:T1) = // we now know the type of both
ourselves (T2) and the second argument (T1)

 ...
}

Any suggestions?



--
Dr Matthew PocockIntegrative Bioinformatics Group, School of Computing Science, Newcastle University mailto: turingatemyhamster@gmail.com gchat: turingatemyhamster@gmail.commsn: matthew_pocock@yahoo.co.uk irc.freenode.net: drdozerskype: matthew.pococktel: (0191) 2566550mob: +447535664143

Matthew Pocock 3
Joined: 2010-07-30,
User offline. Last seen 42 years 45 weeks ago.
Re: Best way to do multiple virtual dispatch?
Yeah, you are right - it certainly is compile-time dispatch, not dynamic. My bad.
Matthew

On 20 January 2012 17:57, Rüdiger Klaehn <rklaehn@googlemail.com> wrote:
Won't that only work if the type of the arguments is known at compile time? Implicit resolution is done at compile time, right? So in the example below, if I have
val a:Any = "bla" val b:Any = 3val c:Any = a + b
He will implicitly convert a to an Addable, then look for an implicit BinOp[Any, Any, Any] and fail to find one.
Here is the code with the + operator changed to ~+~ since String already has a + operator:
trait BinOp[A, B, T] {  def apply(a: A, b: B): T}
case class Addable[A](a: A) {  def ~+~[B, T](b: B)(implicit op: BinOp[A, B, T]): T = op(a, b) }
implicit def asAddable[A](a: A): Addable[A] = Addable(a)
implicit object StringIntIntOp extends BinOp[String, Int, Int] {  def apply(s: String, i: Int): Int = s.length + i }
Here is trying it on the repl with types known at compile time:
val a="Bla"val b=1val c=a ~+~ b
defined trait BinOpdefined class AddableasAddable: [A](a: A)Addable[A]defined module StringIntIntOpa: java.lang.String = Blab: Int = 1c: Int = 4
And here is with using Any:
val a2:Any="Bla"val b2:Any=1val c2=a2~+~b2
error: could not find implicit value for parameter op: BinOp[Any,Any,T]
On Fri, Jan 20, 2012 at 4:30 PM, Matthew Pocock <turingatemyhamster@gmail.com> wrote:
Hi rklaehn,
One way to do this is to use a typeclass and implicits. The plumbing looks something like:
trait BinOp[A, B, T] {  def apply(a: A, b: B): T}
case class Addable[A](a: A) {  def +[B, T](b: B)(implicit op: BinOp[A, B, T]): T = op(a, b)}
implicit def asAddable[A](a: A): Addable = Addable(a)


To add an implementation for particular types, provide an implicit implementation like this:
implicit object StringIntIntOp extends BinOp[String, Int, Int] {   def apply(s: String, i: Int): Int = s.length + i}
Matthew
On 19 January 2012 09:39, rklaehn <rklaehn@googlemail.com> wrote:
Hi all,

what is the best way to do dynamic dispatch on more than one
argument?

The situation is as follows: I have two variables a,b which both can
be of type T1,T2,T3,.. and want to execute a binary operation that has
different logic depending on the type of both arguments.

I guess the canonical way to do something like this would be to
pattern match on a pair. But this will be quite slow if the number of
different types I have to handle is large, right?

def sum(arg1:T,arg2:T) = (arg1,arg2)  match {
 case (a:T1, b:T1) => ...
 case (a:T2, b:T1) => ...
 case (a:T2, b:T1) => ...
 case _ => ...
}

A very fast way would be to use double virtual dispatch, but to do
this I would have to add a lot of boilerplate to each of the concrete
types T1, T2, T3... I would prefer to keep the implementation logic
completely outside the types for some operations.

trait T {
 final def +(arg2:T) = arg2.sum1(this)

 protected def sum1(arg1:T) : T

 protected def sum2(arg2:T1) : T

 protected def sum2(arg2:T2) : T
}

trait T1 {
 protected def sum1(arg1:T) = arg.sum2t1(this)

 protected  def sum2(arg2:T1) = // we now know the type of both
ourselves (T1) and the second argument (T1)

 ...
}

trait T2 {
 protected def sum1(arg1:T) = arg.sum2t2(this)

 protected def sum2(arg2:T1) = // we now know the type of both
ourselves (T2) and the second argument (T1)

 ...
}

Any suggestions?



--
Dr Matthew PocockIntegrative Bioinformatics Group, School of Computing Science, Newcastle University mailto: turingatemyhamster@gmail.com gchat: turingatemyhamster@gmail.commsn: matthew_pocock@yahoo.co.uk irc.freenode.net: drdozerskype: matthew.pococktel: (0191) 2566550mob: +447535664143




--
Dr Matthew PocockIntegrative Bioinformatics Group, School of Computing Science, Newcastle Universitymailto: turingatemyhamster@gmail.com gchat: turingatemyhamster@gmail.commsn: matthew_pocock@yahoo.co.uk irc.freenode.net: drdozerskype: matthew.pococktel: (0191) 2566550mob: +447535664143
Rüdiger Klaehn
Joined: 2009-06-02,
User offline. Last seen 42 years 45 weeks ago.
Re: Best way to do multiple virtual dispatch?
No problem. 
I guess there are a lot of people that think that the whole inheritance thing which allows the runtime type to be different from the type at compile time is unnecessary in a functional language. 
I would be really interested in how I could solve the kind of problems I am currently working on (a complex GUI application that at least attempts to use only pure functions) without using inheritance. 
It might be that it is just two decades of working with OO languages, but I always end up using inheritance for solving certain problems (hierarchy of GUI components).
By the way: is it possible to get all implicit values of a certain type? For example collect all BinOp[_,_,_] in scope? Then you could do a "fallback implicit dispatcher" of type BinOp[Any,Any,T] and do dynamic dispatch in those cases where the type information at compile time is not sufficient to determine which implicit to use...

On Sat, Jan 21, 2012 at 12:34 PM, Matthew Pocock <turingatemyhamster@gmail.com> wrote:
Yeah, you are right - it certainly is compile-time dispatch, not dynamic. My bad.
Matthew

On 20 January 2012 17:57, Rüdiger Klaehn <rklaehn@googlemail.com> wrote:
Won't that only work if the type of the arguments is known at compile time? Implicit resolution is done at compile time, right? So in the example below, if I have
val a:Any = "bla" val b:Any = 3val c:Any = a + b
He will implicitly convert a to an Addable, then look for an implicit BinOp[Any, Any, Any] and fail to find one.
Here is the code with the + operator changed to ~+~ since String already has a + operator:
trait BinOp[A, B, T] {  def apply(a: A, b: B): T}
case class Addable[A](a: A) {  def ~+~[B, T](b: B)(implicit op: BinOp[A, B, T]): T = op(a, b) }
implicit def asAddable[A](a: A): Addable[A] = Addable(a)
implicit object StringIntIntOp extends BinOp[String, Int, Int] {  def apply(s: String, i: Int): Int = s.length + i }
Here is trying it on the repl with types known at compile time:
val a="Bla"val b=1val c=a ~+~ b
defined trait BinOpdefined class AddableasAddable: [A](a: A)Addable[A]defined module StringIntIntOpa: java.lang.String = Blab: Int = 1c: Int = 4
And here is with using Any:
val a2:Any="Bla"val b2:Any=1val c2=a2~+~b2
error: could not find implicit value for parameter op: BinOp[Any,Any,T]
On Fri, Jan 20, 2012 at 4:30 PM, Matthew Pocock <turingatemyhamster@gmail.com> wrote:
Hi rklaehn,
One way to do this is to use a typeclass and implicits. The plumbing looks something like:
trait BinOp[A, B, T] {  def apply(a: A, b: B): T}
case class Addable[A](a: A) {  def +[B, T](b: B)(implicit op: BinOp[A, B, T]): T = op(a, b)}
implicit def asAddable[A](a: A): Addable = Addable(a)


To add an implementation for particular types, provide an implicit implementation like this:
implicit object StringIntIntOp extends BinOp[String, Int, Int] {   def apply(s: String, i: Int): Int = s.length + i}
Matthew
On 19 January 2012 09:39, rklaehn <rklaehn@googlemail.com> wrote:
Hi all,

what is the best way to do dynamic dispatch on more than one
argument?

The situation is as follows: I have two variables a,b which both can
be of type T1,T2,T3,.. and want to execute a binary operation that has
different logic depending on the type of both arguments.

I guess the canonical way to do something like this would be to
pattern match on a pair. But this will be quite slow if the number of
different types I have to handle is large, right?

def sum(arg1:T,arg2:T) = (arg1,arg2)  match {
 case (a:T1, b:T1) => ...
 case (a:T2, b:T1) => ...
 case (a:T2, b:T1) => ...
 case _ => ...
}

A very fast way would be to use double virtual dispatch, but to do
this I would have to add a lot of boilerplate to each of the concrete
types T1, T2, T3... I would prefer to keep the implementation logic
completely outside the types for some operations.

trait T {
 final def +(arg2:T) = arg2.sum1(this)

 protected def sum1(arg1:T) : T

 protected def sum2(arg2:T1) : T

 protected def sum2(arg2:T2) : T
}

trait T1 {
 protected def sum1(arg1:T) = arg.sum2t1(this)

 protected  def sum2(arg2:T1) = // we now know the type of both
ourselves (T1) and the second argument (T1)

 ...
}

trait T2 {
 protected def sum1(arg1:T) = arg.sum2t2(this)

 protected def sum2(arg2:T1) = // we now know the type of both
ourselves (T2) and the second argument (T1)

 ...
}

Any suggestions?



--
Dr Matthew PocockIntegrative Bioinformatics Group, School of Computing Science, Newcastle University mailto: turingatemyhamster@gmail.com gchat: turingatemyhamster@gmail.commsn: matthew_pocock@yahoo.co.uk irc.freenode.net: drdozerskype: matthew.pococktel: (0191) 2566550mob: +447535664143




--
Dr Matthew PocockIntegrative Bioinformatics Group, School of Computing Science, Newcastle Universitymailto: turingatemyhamster@gmail.com gchat: turingatemyhamster@gmail.commsn: matthew_pocock@yahoo.co.uk irc.freenode.net: drdozerskype: matthew.pococktel: (0191) 2566550mob: +447535664143

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland