DEV Community

loading...

Multiple dispatch

jj profile image Juan Julián Merelo Guervós ・6 min read

Multiple versions of "hilos"
Sometimes you want to get things done to a piece of data, but you don't know in advance what that piece of data is made of. It can be an array, it can be a string, it can be a more complicated arrangement of data. Happens all the time in dynamically typed languages: you want to do something to a list of things, such as printing it or simply converting it to a string. You can jump to hoops and do a switch clause and check type and dispatch the corresponding operation. Or you can use multiple dispatch.

Many languages use multiple dispatch

And they use it to bind a function to a type. For instance, Elixir, a Rubyfication of Erlang that turns it into something that can be understood by mere humans:

defprotocol Stringifier do
  @doc "Can convert to string using tostring"
  def tostring( whatever)
end

defmodule Span do
  @doc "A few words"
  defstruct words: []
end

defimpl Stringifier, for: Span do
  def tostring(span), do: Enum.join(span.words," ")
end

defmodule Quoted do
  @doc "A few words and a quote"
  defstruct words: [], quote: "em"

  def i(string, quote) do
    "<#{quote}> #{string} </#{quote}>"
  end
end

defimpl Stringifier, for: Quoted do
  def tostring(quoted), do: Quoted.i(Enum.join(quoted.words," "),quoted.quote)
end

Some languages require the programmer to declare, in advance, the function we intend to use for multiple dispatch; in elixir parlance, they are called protocos. We need to declare a protocol with the name of the function and the function signature. In this case, it takes a single artument, which we are going to be calling whatever. We will call the protocol Stringifier.
We will use the marked text model here as we have done so far. We have a Span which is a simple list of words, and a Quoted which adds some kind of quoting construct around it. Modules in Elixir are packaging constructs, but are in no way objects: they are structs and stuff you can do to them, but don't get instantiated or anything like that. But they at least define a structure we can use later on to define the multiple dispatching:

defimpl Stringifier, for: Span do
  def tostring(span), do: Enum.join(span.words," ")
end

Since a protocol is kind of an abstract interface, we define a function that follows that interface by declaring an implementation using defimpl; defimpl Stringifier defines an implementation of that protocol. Applied to what? for: Span says this is going to be applied to that specific data struct. The actual implementation of the function follows, and it wraps around the joined words the quote we have defined. The other implementation follows the same, but when we want to use it:

defmodule Main do
  def main do
    words = %Span{words: ["A","few","words"]}
    IO.puts(Stringifier.tostring(words))
    quoted = %Quoted{words: ["More","words"], quote: "em"}
    IO.puts(Stringifier.tostring(quoted))
  end
end

Main.main

No matter what we use, IO.puts(Stringifier.tostring(quoted)) will detect the data type and will dispatch to the corresponding code the message so that it gets printed alright.

And we know that Perl6 throws everything and the kitchen sink in. Let's see

Multiple dispatch in Perl6

We have seen a bit of that already. Grammars use multiple dispatch to deal with symbols that have pretty much the same function, as in quoting constructs. Since grammars are classes, multiple-dispatching rules which are methods make sense. But let's go back to the actual data structures that would represent paragraphs with enrichment:

role stringifiable {
    method to-string( Str $ligature ) { ... }
}

# Thanks to https://perl6advent.wordpress.com/2009/12/18/day-18-roles/
role Span does stringifiable {
    has @.words;
    method to-string( Str $ligature ) {
    return @!words.join( $ligature );
    }
}

class Quoted does Span {
    has $.quote;

    method to-string( Str $ligature ) {
    return self.þ ~  self.Span::to-string( $ligature ) ~ self.ø;
    }

    method þ () {
    return "<" ~ $!quote ~ ">";
    }

    method ø () {
    return "</" ~ $!quote ~ ">";
    }

}

class Heading does Span {

    submethod BUILD( :@!words ) {
    }

    method to-string() {
    return "\n<h1>" ~ self.Span::to-string( " " ) ~ "</h1>\n";
    }
}

It's rather longish because we are throwing a bit of everything we have done so far in. We define a lightweight role which is just a function, with no implementation. The { ... } takes care of that. We defer the implementation to the role users, and we actually force them to do so. We have another role, Span, which is consumed by two classes, Quoted for enriched words and Heading for header class. This is a more full-fledged class with its BUILD function and all, but it needs that since it's got to tie the object instantiation function (new) to the instance variables. Been there, done that. No big deal. But now we might need to convert to string some object whose class we do not know in advance. Multiple dispatch to the rescue:

# Stringifier for stuff above
proto stringify-chunk( | )                          {*}
multi stringify-chunk( $chunk where Quoted | Span ) {
    return $chunk.to-string( " " );
}
multi stringify-chunk( Heading $heading )           {
    return $heading.to-string();
}

We use exactly the same syntax we used before in grammars. A proto says "Hey, this is the way the multiply dispatched functions are going to look". Proto as in protocol, or maybe prototype, but protocol suits me just fine since it's how they are called in Elixir and we have seen before. However, Elixir used a placeholder to say that the implementations were going to use a single word. Perl6 is more flexible: | is a Capture which says "I don't care what you throw in here, I'll take care of it".
The instantiations of the proto(type|col) use multi, as in multiple dispatch. They do not need to declare that they are part of that protocol, just the general shape of it takes care of that. But the first one use again | in a different way. We got Span and Quoted, but they are pretty much the same, so they can use the same implementation. $chunk where Quoted | Span uses two features in Perl6. Quoted | Span is a Junction whose value is one or the other. And Perl6 allows to add constrains to type declarations via where. $chunk can be either a Quoted or a Span, and we declare it in a very compact fashion.

Another proof that Perl6 gets stuff done. We have here a multi function that deals with multiple types too.

We will use this function from here:

class Paragraph {
    has stringifiable @!chunks;

    method new( *@chunks ) {
    return self.bless(:@chunks);
    }

    submethod BUILD( :@!chunks ) {}

    method to-string() {
    return "\n" ~ (@!chunks.map: { stringify-chunk( $^þ ) }).join(" " );
    }
}

Which is a class with new and all. This kind of function is a method (can be inherited) and not a submethod (which can't) and allows us to make a more compact definition that does not use named parameters. *@chunks is a slurpy parameters (uses *) which takes everything and puts it into an array @chunks, which we then bless and turn into the instance variable. But @chunks does stringifiable. We don't know what every piece of the text will be in advance, but we do know we can string it. That is why we call { stringify-chunk( $^þ ) } on it later on.

How can we use it?

As usual, the full code is in GitHub

my $heading  = Heading.new( words => "Here we are".comb(/\w+/) );
my $span     = Span.new( words => "This goes first");
my $quoted   = Quoted.new( words => "This is last",
              quote => "em" );


my $sentence = Paragraph.new( $heading, $span, $quoted );

say $sentence.to-string();

We define three different kinds of objects, which get slurped when defining a Paragraph, and eventually are printed by calling the multi stringify-chunk on them using map. The result is a not incredibly beautiful but serviceable

<h1>Here we are</h1>
 This goes first <em>This is last</em>

To be continued

Grammars are kind of the nexus of everything that is new and beautiful about Perl6. We will continue expanding the grammar with new features to make it almost, but not quite, a Markdown grammar.

Discussion

pic
Editor guide