README
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 > piDescription
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, barGets 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"]); #-> FooDispatches 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',Ļ); #-> piMakes 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',Ļ); #-> piOverrides 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