Rand Stats

DispatchMap

github:LLFourn

DispatchMap

A map that uses Perl 6 multi dispatch to link keys to values

Synopsis

need DispatchMap;

my $map = DispatchMap.new(
         foo => (
           (Int) =>  "an Int!",
           (subset :: of Int:D where * > 5) => "Wow, an Int greater than 5",
           ("literal text")           => "The text 'literal text'",
           (π)               => "pi",
           (Str)             => "one string",
           (Stringy)         => "something stringy",
           (Str,Str)         => "two strings";
         ),
         bar => ( (τ) => "Tau > pi" )).compose;

say $map.get("foo",2); #-> an Int!
say $map.get("foo",6); #-> Wow, an Int greater than 5
say $map.get("foo","literal text"); #-> A literal foo;
say $map.get-all("foo","literal text"); #-> ("The text 'literal text'","one string","something stringy");
say $map.get("foo",π); #-> pi
say $map.get("foo","one","two"); #-> two strings
say $map.get("bar",τ); # Tau > pi

Description

warning this is module is experimental and subject to change

warning this module uses unspec'd rakudo internals and could break without warning

Perl 6 has a very sophisticated routine dispatch system based on finding the candidate that matches the call's arguments most narrowly. Unfortunately there is no way (yet) to make use of the dispatching logic outside of routine calls. This module exposes that logic in a map like interface.

The following are different ways of achieving the same result:

# Using builtin dispatchers
class Parent {
    multi method foo(Str:D $str) { "a string: $str" }
    multi method foo(Int:D $int) { "an int: $int"   }
    multi method foo(42)         { "a special int"  }
}

say Parent.foo("lorem");
say Parent.foo(42);
my $meth = Stuff.find_method("foo").cando(\("lorem"))[0];

class Child is Parent {
    multi method foo(π) { "pi"  }
}

say Child.foo(π);
say Child.foo(42);
# Using DispatchMap
use DispatchMap;
my $parent = DispatchMap.new(
    foo => (
        (Str:D) => -> $str { "a string: $str" },
        (Int:D) => -> $int { "an int: $int" },
        (21 + 21)    => -> $int { "a special int" }
    )
).compose;

say $parent.dispatch("foo","lorem");
say $parent.dispatch("foo",42);
my $block = $map.get("foo","lorem");

my $child = DispatchMap.new
.add-parent($parent)
.append(foo => ( (π) => { "pi" } ))
.compose;


say $child.dispatch('foo',π);
say $child.dispatch('foo',42);

The main use of a DispatchMap is to create method signatures at runtime that dispatch in the same order as normal methods. Internally, DispatchMap creates new meta-objects at runtime and attaches methods to them with signatures created from the keys with nqp. As a result, .compose must be called before the DispatchMap can be used

Methods

new(*%namespaces)

my $map = DispatchMap.new(
  foo => ((Int,Array) => "Foo", (Cool) => "Bar") ),
  bar => ((Str) => "Baz")
);
#or
my $map = DispatchMap.new(
  foo => ((Int,Array),"Foo",(Cool),"Bar"),
  bar => ( (Str), "Baz" )
);
#or
my $map = DispatchMap.new(
  foo => [(Int,Array),"Foo",(Cool),"Bar"],
  bar => [ (Str),"Baz" ]
);

Makes a new DispatchMap where the keys are the namespaces and the values are signature-value pairs. You can think of a namespace as a dispatchter method and the signature-value pairs as multi candidates.

Presently, the psudo-signatures you pass to .new and .append are limited to non-slurpy positional parameters. If it's passed a type object that will be used as the nominal type of the parameter. If a literal is passed the .WHAT of the object is used as the nominal type and the literal is used as a where constraint.

note you won't be able to use the map until you've called compose.

compose

my $map = DispatchMap.new(
  foo => ((Int,Array) => "Foo", (Cool) => "Bar") ),
  bar => ((Str) => "Baz")
).compose;

Composes the dispatcher.

namespaces

my $map = DispatchMap.new(
  foo => ((Int,Array) => "Foo", (Cool) => "Bar") ),
  bar => ((Str) => "Baz")
).compose;
say $map.namespaces; #-> foo, bar

Gets the all the namespaces in the DispatchMap.

note this method won't dispatch properly until .compose has been called

keys(Str:D $ns)

my $map = DispatchMap.new( foo => ((Int,Array) => "Foo", (Cool) => "Bar") ).compose;
say $map.keys('foo'); #-> (Int,Array),(Cool)

Gets the keys for a namepsace as a list of lists.

note this method won't work properly until .compose has been called

values(Str:D $ns)

my $map = DispatchMap.new( foo => ((Int,Array) => "Foo", (Cool) => "Bar") ).compose;
say $map.values('foo'); #-> (Int,Array),(Cool)

Gets the values for a name pace.

note this method won't work properly until .compose has been called

pairs(Str:D $ns)

my $map = DispatchMap.new( foo => ((Int,Array) => "Foo", (Cool) => "Bar") ).compose;
say $map.pairs('foo'); #-> (Int,Array) => "Foo",(Cool) => "Bar"

Gets the key-value pairs for a namespace.

note this method won't work properly until .compose has been called

list(Str:D $ns)

my $map = DispatchMap.new( foo => ((Int,Array) => "Foo", (Cool) => "Bar") ).compose;
say $map.list('foo'); #-> (Int,Array),"Foo",(Cool),"Bar"

Returns a list of keys and values for a namespace.

note this method won't work properly until .compose has been called

get(Str:D $ns,|key)

my $map = DispatchMap.new( foo => ((Int,Array) => "Foo", (Cool) => "Bar") ).compose;
say $map.get('foo',1,["one","two"]); #-> Foo

Dispatches to a namespace, returning the associated value.

note this method won't dispatch properly until .compose has been called

get-all(Str:D $ns,|key)

my $map = DispatchMap.new(
  number-types => (
    Numeric => "A number",
    Real => "A real number",
    Int => "An int",
    (π)  => "pi"
  )
).compose;
say $map.get-all('number-types',π); # "pi", "Real", "Numeric";

Dispatches to a namespace, returning the values that match the capture in order of narrowness (internally uses cando).

note this method won't dispatch properly until after .compose has been called

dispatch(Str:D $ns,|key)

my $map = DispatchMap.new(
  abstract-join => (
    (Str:D,Str:D) => { $^a ~ $^b },
    (Iterable:D,Iterable:D) => { |$^a,|$^b },
    (Numeric:D,Numeric:D) => { $^a + $^b }
  )
).compose;

say $map.dispatch('abstract-join',"foo","bar"),"foobar"; #-> foobar
say $map.dispatch('abstract-join',<one two>,<three four>); #-> one two three four
say $map.dispatch('abstract-join',1,2); #-> 3

.dispatch works like .get except the if the result is a Callable it will invoke it with the arguments you pass to .dispatch and return the result.

note this method won't dispatch properly until .compose has been called

append(*%namespaces)

my $map = DispatchMap.new( my-namespace => ((Int,Array) => "Foo", (Cool) => "Bar") );
$map.append(my-namespace => ((Real,Real) => "Super Real!")).compose;
say $map.get('my-namespace',π,τ); #-> Super Real!

Appends the values to the corresponding namespaces. Takes the arguments in the same format as .new.

note this method won't affect dispatching until .compose is called

ns-meta(Str:D $ns) is rw

my $map = DispatchMap.new(
   foo => (
     (Real,Str) => "real str",
     (Int,Str) => "int str",
   ),
   bar => ((Int,Int) => "int int")
).compose;

$map.ns-meta('foo') = "foo stores some number and string dispatches";
$map.ns-meta('bar') = "bar just has some ints";

Returns a writeable container associated with the given namespace. You can think of this a map that runs in parallel to the dispatching map. it is inherited by child DipatchMaps and overwritten by .override.

ns-meta will work before or after .compose has been called.

add-parent(DispatchMap:D $parent)

my $parent = DispatchMap.new(
  number-types => (
    (Numeric) => "A number",
    (Int) => "An int",
  )
).compose;

my $child = DispatchMap.new
.add-parent($parent)
.append( number-types => ( (π) => "pi" ),)
.compose;

say $child.get('number-types',3.14); #-> A number
say $parent.get('number-types',π); #-> A number
say $child.get('number-types',π); #-> pi

Makes another DispatchMap the parent of the DispatchMap. This means the internal object used to hang methods on will inherit from the parent.

note Will error if .compose has already been called.

override(*%namespaces)

my $parent = DispatchMap.new(
  number-types => (
    (Numeric) => "A number",
    (Int) => "An int",
  )
).compose;

my $child = DispatchMap.new
.add-parent($parent)
.override( number-types => ( (π) => "pi" ) )
.compose;


say $child.get('number-types',3.14); #-> Nil
say $child.get('number-types',π); #-> pi

Overrides the dispatcher for a namepsace rather than adding to it. Overridden namespaces get their own .ns-meta.

note this method won't affect dispatching until .compose is called