Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing-in roles in traits apparently not working

Tags:

traits

raku

This example is taken from roast, although it's been there for 8 years:

role doc { has $.doc is rw }

multi trait_mod:<is>(Variable $a, :$docced!) {
    $a does doc.new(doc => $docced);
}

my $dog is docced('barks');
say $dog.VAR;

This returns Any, without any kind of role mixed in. There's apparently no way to get to the "doc" part, although the trait does not error. Any idea?

like image 942
jjmerelo Avatar asked May 01 '19 07:05

jjmerelo


Video Answer


1 Answers

(This answer builds on @guifa's answer and JJ's comment.)

The idiom to use in variable traits is essentially $var.var.VAR.

While that sounds fun when said aloud it also seems crazy. It isn't, but it demands explanation at the very least and perhaps some sort of cognitive/syntactic relief.

Here's the brief version of how to make some sense of it:

  • $var makes sense as the name of the trait parameter because it's bound to a Variable, a compiler's-eye view of a variable.

  • .var is needed to access the user's-eye view of a variable given the compiler's-eye view.

  • If the variable is a Scalar then a .VAR is needed as well to get the variable rather than the value it contains. (It does no harm if it isn't a Scalar.)

Some relief?

I'll explain the above in more detail in a mo, but first, what about some relief?

Perhaps we could introduce a new Variable method that does .var.VAR. But imo this would be a mistake unless the name for the method is so good it essentially eliminates the need for the $var.var.VAR incantation explanation that follows in the next section of this answer.

But I doubt such a name exists. Every name I've come up with makes matters worse in some way. And even if we came up with the perfect name, it would still barely be worth it at best.

I was struck by the complexity of your original example. There's an is trait that calls a does trait. So perhaps there's call for a routine that abstracts both that complexity and the $var.var.VAR. But there are existing ways to reduce that double trait complexity anyway, eg:

role doc[$doc] { has $.doc is rw = $doc}
my $dog does doc['barks'];
say $dog.doc; # barks

A longer explanation of $var.var.VAR

But $v is already a variable. Why so many var and VARs?

Indeed. $v is bound to an instance of the Variable class. Isn't that enough?

No, because a Variable:

  • Is for storing metadata about a variable while it's being compiled. (Perhaps it should have been called Metadata-About-A-Variable-Being-Compiled? Just kidding. Variable looks nice in trait signatures and changing its name wouldn't stop us needing to use and explain the $var.var.VAR idiom anyway.)

  • Is not the droid we are looking for. We want a user's-eye view of the variable. One that's been declared and compiled and is then being used as part of user code. (For example, $dog in the line say $dog.... Even if it were BEGIN say $dog..., so it ran at compile-time, $dog would still refer to a symbol that's bound to a user's-eye view container or value. It would not refer to the Variable instance that's only the compiler's-eye view of data related to the variable.)

  • Makes life easier for the compiler and those writing traits. But it requires that a trait writer accesses the user's-eye view of the variable to access or alter the user's-eye view. The .var attribute of the Variable stores that user's-eye view. (I note the roast test has a .container attribute that you omitted. That's clearly now been renamed .var. My guess is that that's because a variable may be bound to an immutable value rather than a container so the name .container was considered misleading.)

So, how do we arrive at $var.var.VAR?

Let's start with a variant of your original code and then move forward. I'll switch from $dog to @dog and drop the .VAR from the say line:

multi trait_mod:<is>(Variable $a, :$docced!) {
  $a does role { has $.doc = $docced }
}

my @dog is docced('barks');
say @dog.doc; # No such method 'doc' for invocant of type 'Array'

This almost works. One tiny change and it works:

multi trait_mod:<is>(Variable $a, :$docced!) {
  $a.var does role { has $.doc = $docced }
}

my @dog is docced('barks');
say @dog.doc; # barks

All I've done is add a .var to the ... does role ... line. In your original, that line is modifying the compiler's-eye view of the variable, i.e. the Variable object bound to $a. It doesn't modify the user's-eye view of the variable, i.e. the Array bound to @dog.

As far as I know everything now works correctly for plural containers like arrays and hashes:

@dog[1] = 42;
say @dog;     # [(Any) 42]
say @dog.doc; # barks

But when we try it with a Scalar variable:

my $dog is docced('barks');

we get:

Cannot use 'does' operator on a type object Any.

This is because the .var returns whatever it is that the user's-eye view variable usually returns. With an Array you get the Array. But with a Scalar you get the value the Scalar contains. (This is a fundamental aspect of P6. It works great but you have to know it in these sorts of scenarios.)

So to get this to appear to work again we have to add a couple .VAR's as well. For anything other than a Scalar a .VAR is a "no op" so it does no harm to cases other than a Scalar to add it:

multi trait_mod:<is>(Variable $a, :$docced!) {
  $a.var.VAR does role { has $.doc = $docced }
}

And now the Scalar case also appears to work:

my $dog is docced('barks');
say $dog.VAR.doc; # barks

(I've had to reintroduce the .VAR in the say line for the same reason I had to add it to the $a.var.VAR ... line.)

If all were well that would be the end of this answer.

A bug

But something is broken. If we'd attempted to initialize the Scalar variable:

my $dog is docced('barks') = 42;

we'd see:

Cannot assign to an immutable value

As @guifa noted, and I stumbled on a while back:

It seems that a Scalar with a mixin no longer successfully functions as a container and the assignment fails. This currently looks to me like a bug.

like image 169
raiph Avatar answered Oct 11 '22 00:10

raiph