49. Dumping 0 but True in Perl 6

Yesterday, we talked about how modification of the value such as 0 but True works in Rakudo Perl 6. Today, we’ll try to fix a missing fragment in the implementation, which does not let you correctly dumping an object with the perl method (there is a bug report #126097 for that).

Let us see what happens now:

$ perl6
To exit type 'exit' or '^D'
> my $v = 0 but True;
0
> $v.perl
0

The result of calling perl is 0, so the information about the mixed-in Boolean role is lost. Our goal for today is to understand why and how to fix that.

First of all, we should realise which routine is triggered when you call it as $v.perl. If you add a Boolean role, you effectively add the Bool method to the (cloned) object:

> $v.^methods
(Bool Range asech perl truncate acosech sin ACCEPTS asinh floor abs asec cosec acotan ceiling acos acosec unpolar exp acotanh gist Num round DUMP asin log10 expmod msb FatRat isNaN cotanh atanh sign sqrt Bridge atan2 tan acosh conj narrow base Str Numeric new pred cosh sech log Rat roots cotan sinh tanh is-prime Bool sec Real Int cis cos rand succ chr polymod Capture WHICH lsb cosech Complex atan)

If you mix-in, for example, an integer, you add an Int method:

> my $x = 0 but 42;
0
> $x.Int
42
> $x.^methods
(Int Range asech perl truncate acosech sin ACCEPTS asinh floor abs asec cosec acotan ceiling acos acosec unpolar exp acotanh gist Num round DUMP asin log10 expmod msb FatRat isNaN cotanh atanh sign sqrt Bridge atan2 tan acosh conj narrow base Str Numeric new pred cosh sech log Rat roots cotan sinh tanh is-prime Bool sec Real Int cis cos rand succ chr polymod Capture WHICH lsb cosech Complex atan)

In both examples, the perl method comes from the Int class:

multi method perl(Int:D:) {
    self.Str;
}

It knows nothing about the but clause. One of the ways is to modify it so that it tries to test if there are mixed-in roles in the object. Probably, this is not the best solution as it affects all the other objects that might use the method.

Let us solve it differently and instead of teaching the ‘global’ method just add our own method to the object at the moment the but infix is processed. Refer to the previous post to refresh the details about the infix.

First, add a method that only prints the but part. Add the following lines to the GENERATE-ROLE-FROM-VALUE function:

my $perl_meth := method () { " but $val" };
$perl_meth.set_name('perl');
$role.^add_method('perl', $perl_meth);

This is done exactly in the same way as the Bool method would be added for 0 but True.

Compile and test:

> my $v = 0 but True;
0
> $v.perl
 but True

It works as expected but, of course, only the alternative value is printed. Also, check the list of the methods on our object:

> $v.^methods
(perl Bool Range asech perl truncate acosech sin ACCEPTS asinh floor abs asec cosec acotan ceiling acos acosec unpolar exp acotanh gist Num round DUMP asin log10 expmod msb FatRat isNaN cotanh atanh sign sqrt Bridge atan2 tan acosh conj narrow base Str Numeric new pred cosh sech log Rat roots cotan sinh tanh is-prime Bool sec Real Int cis cos rand succ chr polymod Capture WHICH lsb cosech Complex atan)

You see that there are two perl methods now. And, actually, to dump the original value, we just need to call the original method and concatenate its result with our additional string. Unfortunately, it is not that easy to do that from the GENERATE-ROLE-FROM-VALUE function as it only takes a value, not the object or its class. You can try using the $?CLASS variable or just copy the body of the function to the infix body, which I did and got the following code. Notice how I call the original perl method:

multi sub infix:<but>(Mu \obj, Mu:D $val) is raw {
    my $role := Metamodel::ParametricRoleHOW.new_type();
    my $meth := method () { $val };
    $meth.set_name($val.^name);
    $role.^add_method($meth.name, $meth);

    my $perl_meth := method () { obj.perl ~ " but $val" };
    $perl_meth.set_name('perl');
    $role.^add_method('perl', $perl_meth);

    $role.^set_body_block(
        -> |c { nqp::list($role, nqp::hash('$?CLASS', c<$?CLASS>)) });

    obj.clone.^mixin($role.^compose);
}

Compile and test:

> my $v = 0 but True;
0
> $v.perl
0 but True

> $v = 42 but False;
42
> $v.perl
42 but False
> ?$v
False

The goal has been achieved. The only thing that is not really cool is duplication of code. What if we just pass the prepared version of the perl method to the routine, and it will compose it to the object?

multi sub infix:<but>(Mu \obj, Mu:D $val) is raw {
    my $perl_meth := method () { obj.perl ~ " but $val" };

    obj.clone.^mixin(GENERATE-ROLE-FROM-VALUE($val, $perl_meth));
}

sub GENERATE-ROLE-FROM-VALUE($val, $perl_meth?) {
    my $role := Metamodel::ParametricRoleHOW.new_type();
    my $meth := method () { $val };
    $meth.set_name($val.^name);
    $role.^add_method($meth.name, $meth);

    if $perl_meth {
        $perl_meth.set_name('perl');
        $role.^add_method('perl', $perl_meth);
    }

    $role.^set_body_block(
        -> |c { nqp::list($role, nqp::hash('$?CLASS', c<$?CLASS>)) });
    $role.^compose;
}

Here, I pass the method as an optional parameter, as the function is also used in other places.

$ ./perl6
To exit type 'exit' or '^D'
> my $v = 0 but True;
0

> $v.perl
0 but True

Alternatively, we could make a multi-method. I will ask what Perl 6 people think in the IRC channel and let you know. Update: I asked and I won’t fix that 😀

One thought on “49. Dumping 0 but True in Perl 6

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s