objectattributesimmutabilitytraitsraku

Is it possible to create an attribute trait that set a meta-attribute?


I try to create an attribute trait. The use case is to mark some attributes of a class as "crudable" in the context of an objects-to-documents-mapping while other are not.

role crud {
    has Bool $.crud is default(True);
}

multi trait_mod:<is>(Attribute $a, crud, $arg) {
    $a.container.VAR does crud($arg);
}

class Foo {
    has $.bar is rw;

    # Provide an extra nested information
    has $.baz is rw is crud(True);
}

By reading and adapting some example code, I managed to get something that seems to do what I want. Here is a snippet with test case.

When I instantiate a new Foo object and set the $.bar attribute (that is not crud), it looks like that:

.Foo @0
├ $.bar is rw = 123456789
└ $.baz is rw = .Scalar+{crud} @1
  └ $.crud +{crud} = True

What I understand from this is that the $.baz attribute got what I call a meta-attribute that is independent from its potential value.

It looks good to me (if I understood correctly what I did here and that my traits use is not a dirty hack). It is possible to reach $foo.baz.crud that is True. Though, I don't understand very well what .Scalar+{crud} means, and if I can set something there and how.

When I try to set the $.baz instance attribute, this error is returned:

Cannot modify an immutable Scalar+{crud} (Scalar+{crud}.new(crud => Bool::True))
  in block <unit> at t/08-attribute-trait.t line 30

Note: This is the closest thing to a working solution I managed to get. I don't need different crud settings for different instances of instantiated Foo classes.

I never want to change the value of the boolean, in fact, once the object instantiated, just providing it to attributes with is crud. I am not even interested to pass a True or False value as an argument: if it would be possible to just set the boolean trait attribute to True by default, it would be enough. I didn't manage to do this though, like:

multi trait_mod:<is>(Attribute $a, :$crud!) {
    # Something like this
    $a.container.VAR does set-crud;
}

class Foo {
    has $.bar is rw;
    has $.baz is rw is crud;
}

Am I trying to do something impossible? How could I adapt this code to achieve this use case?


Solution

  • There are several things going on here. First of all, the signature of the trait_mod looks to be wrong. Secondly, there appears to be a bad interaction when the name of a trait is the same as an existing role. I believe this should be an NYI exception, but apparently it either goes wrong in parsing, or it goes wrong in trying to produce the error message.

    Anyways, I think this is what you want:

    role CRUD {};  # since CRUD is used as an acronym, I chose to use uppercase here
    
    multi trait_mod:<is>(Attribute:D $a, :$crud!) { # note required named attribute!
        $a.^mixin: CRUD if $crud;  # mixin the CRUD role if a True value was given
    }
    
    class A {
        has $.a is crud(False);  # too bad "is !crud" is invalid syntax
        has $.b is crud;
    }
    
    say "$_.name(): { $_ ~~ CRUD }" for A.^attributes; # $!a: False, $!b: True
    

    Hope this helps.