6

Is it possible to override an attribute of a role to provide a default?

role A {
     has $.a;
}
class B does A {
    has $.a = "default";
}
my $b = B.new;

This results in a compile error:

===SORRY!=== Error while compiling:
Attribute '$!a' already exists in the class 'B', but a role also wishes to compose it
J Hall
  • 531
  • 3
  • 10

2 Answers2

5

Since methods in R may refer to $!a there would be ambiguity what attribute is referred to.

Use the submethod BUILD to initialise inherited/mixedin attributes.

role R { has $.a };
class C does R {
    submethod BUILD { $!a = "default" }
};
my $c = C.new;
dd $c;
# OUTPUT«C $c = C.new(a => "default")␤»

Depending on your usecase you may be better of to set the default via a role parameter.

role R[$d] { has $.a = $d };
class C does R["default"] { };
my $c = C.new;
dd $c;
# OUTPUT«C $c = C.new(a => "default")␤»
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    "Since methods in R may refer to $!a there would be ambiguity what attribute is referred to." Is that talking about why the OP's approach can not work or about some issue that may arise even with your first solution and which is resolved by your second solution (use of a parametric role)? – raiph Aug 07 '16 at 19:35
2

No, it's not possible to redeclare an attribute - but you can initialize it using the BUILD submethod of the class:

role A {
     has $.a;
}

class B does A {
    submethod BUILD(:$!a = 'default') {}
}

Note that if you just set the value within the body of BUILD instead of its signature via

class B does A {
    submethod BUILD { $!a = 'default' }
}

the user would not be able to overwrite the default by providing a named initializer via B.new(a => 42).

Another, more elegant approach that is especially useful if setting the default is something you expect to do a lot (ie the default value can be regarded as part of the role's interface) is making it a parameter:

role A[$d] {
    has $.a = $d;
}

class B does A['default'] {}
Christoph
  • 164,997
  • 36
  • 182
  • 240