Skip to content

Latest commit

 

History

History
133 lines (105 loc) · 5.53 KB

multi-dispatch.pod

File metadata and controls

133 lines (105 loc) · 5.53 KB
TODO: heading JSON, short for Javascript Object Notation, is a simple data exchange format that is often used for communicating with web services. It supports arrays, hashes, numbers, strings, boolean values and C, the undefined value. The example below presents a section of C, a minimal library for converting Perl 6 data structures to JSON. The other part of that module, which parses JSON and turns it into Perl 6 data structures, will be presented later (TODO: reference grammar chapter). The full code, containing additionally containing documentation and tests, can be found at L. multi to-json(Num $d) { ~$d } multi to-json(Int $d) { ~$d } multi to-json(Bool $d) { $d ?? 'true' !! 'false'; } multi to-json(Str $d) { '"' ~ $d.trans(['"', '\\', "\b", "\f", "\n", "\r", "\t"] => ['\"', '\\\\', '\b', '\f', '\n', '\r', '\t']) ~ '"' } multi to-json(Array $d) { return '[ ' ~ (map { to-json($_) }, $d.values).join(', ') ~ ' ]'; } multi to-json(Hash $d) { return '{ ' ~ (map { to-json(.key) ~ ' : ' ~ to-json(.value) }, $d.pairs).join(', ') ~ ' }'; } multi to-json($d where undef) { 'null' } multi to-json($d) { die "Can't serialize an object of type " ~ $d.WHAT.perl } This code defines a single I sub named C, which takes one argument and turns that into a string. However there are many candidates of that sub: subs with the same name, but different signatures. The various candidates all look like multi to-json(Bool $data) { code here } multi to-json(Num $data) { code here } Which one is actually called depends on the type of the data passed to subroutine. So if you call C, the first one is called. If you pass a C to it, the second one is called. The candidates for handling C and C are very simple: since JSON's and Perl 6's number formats coincide, the JSON converter can simply rely on Perls conversion of these numbers to strings. The C candidate returns the literal strings C<'true'> or C<'false'>. The C candidate does a bit more work: it add quotes at the begin and the end, and substitutes those characters that the JSON spec does not allow in strings - a tabulator by C<\t>, a newline by C<\n> and so on. The next one is C, which converts all elements of the array to JSON, joins them with commas and surrounds them with square brackets. The part that converts the individual elements calls C again. This doesn't necessarily call the candidate it was called from, but again the one that fits to the type of the argument. TODO: describe Hash candidate briefly to-json($d where undef) { 'null' } This candidate adds something new: it doesn't contain a type defintion, and thus the type of the parameter defaults to C, which is the root of the "normal" branch of the type hierarchy (more on that $later). More interestingly there's the C clause, which is a so-called I: it matches only some values of the type C. The verbose version of that is C<$d where { $d ~~ undef }>, so the C part is actually translated to a smart match. And yes, the curly braces can contain arbirary code. Whenever the compiler performs a type check on the parameter C<$d>, it first checks the I type (which is C here), and if that check succeeds, it calls the code block. Only if that returns a true value, the whole type check is considered successful. You can abuse that count how often a type check is performed: my $counter = 0; multi a(Int $x) { }; multi a($x) { } multi a($x where { $counter++; True }) { }; a(3); say $counter; a('str'); say $counter; This piece of code defines three multis, one of which increases a counter whenever its C clause, also called I, is executed. Any Perl 6 compiler is free to optimize away type checks it knows to succeed, but if it does not, the second line with C prints a higher number than the first. (TODO: insert side note "Don't do that at home, kids! Type checks with side effects are a B bad idea in real world code") Back to the JSON example, there's one candidate not yet explained. multi to-json($d) { die "Can't serialize an object of type " ~ $d.WHAT.perl } This has not explicit types or constraint on its parameter at all, so it defaults to C - and thus matches any object we might pass to it. The code just complains that it doesn't know what to do with the argument, because JSON is just defined for some basic structures. That might look simple at first, but if you look closer you find that it doesn't just match for objects of all type for which no other candidate is defined - it matches for I objects. Including C, C, C and so on. So for a call like C there are two matching candidates - C and C. If you try it out, you'll find that the dispatcher (which is the part of the compiler that decides which candidate to call) still calls the C candidate. The explanation is that since C is a type that conforms to C, it is considered a tighter match for an integer. More generally speaking if you have two types C and C, and C conforms to C (or C is the Perl 6 programmer says), then an object which conforsm to C does so more tightly than to C. And in the case of a multi dispatch the tightest match always wins.