Глава 6. Multis


Multiple arguments
Bindability checks
Nested Signatures in Multi-dispatch
Toying with the candidate list

Perl usually decides which function to call based on the name of the function or the contents of a function reference. This is simple to understand. Perl can also examine the contents of the arguments provided to decide which of several variants of a function--variants each with the same name--to call. In this case, the amount and types of the function's arguments help to distinguish between multiple variants of a function. This is multidispatch, and the functions to which Perl can dispatch in this case are multis.

Javascript Object Notation (JSON) is a simple data exchange format often used for communicating with web services. It supports arrays, hashes, numbers, strings, boolean values, and null, the undefined value.

JSON::Tiny is a minimal library used to convert Perl 6 data structures to JSON. See for the other part of that module, which parses JSON and turns it into Perl 6 data structures. The full code, containing additional documentation and tests, is available from http://github.com/moritz/json/. This snippet demonstrates how multis make the code simpler and more obvious:

    multi to-json(Real $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  '[ '
                ~ $d.values.map({ to-json($_) }).join(', ')
                ~ ' ]';

    multi to-json(Hash  $d) {
        return '{ '
                ~ $d.pairs.map({ to-json(.key)
                ~ ' : '
                ~ to-json(.value) }).join(', ')
                ~ ' }';

    multi to-json($d where {!defined $d}) { 'null' }

    multi to-json($d) {
        die "Can't serialize an object of type " ~ $d.WHAT.perl

This code defines a single multi sub named to-json, which takes one argument and turns that into a string. to-json has many candidates; these subs all have the name to-json but differ in their signatures. Every candidate resembles:

    multi to-json(Bool $data) { ... }
    multi to-json(Real $data) { ... }

Which one is actually called depends on the type of the data passed to the subroutine. A call such as to-json(Bool::True) invokes the first candidate. Passing a numeric value of type Real instead invokes the second.

The candidate for handling Real is very simple; because JSON's and Perl 6's number formats coincide, the JSON converter can rely on Perl's conversion of these numbers to strings. The Bool candidate returns a literal string 'true' or 'false'.

The Str candidate does more work: it wraps its parameter in quotes and escapes literal characters that the JSON spec does not allow in strings--a tab character becomes \t, a newline \n, and so on.

The to-json(Array $d) candidate converts all elements of the array to JSON with recursive calls to to-json, joins them with commas, and surrounds them with square brackets. The recursive calls demonstrate a powerful truth of multidispatch: these calls do not necessarily recurse to the Array candidate, but dispatch to the appropriate candidate based on the types of their arguments.

The candidate that processes hashes turns them into the form { "key1" : "value1", "key2" : [ "second", "value" ] }. It does this again by recursing into to-json.