You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.