class Attribute
class Attribute { }
In Raku lingo, an attribute refers to a per-instance/object storage slot.
An Attribute
is used to talk about classes' and roles' attributes at the
metalevel.
Normal usage of attributes does not require the user to use this class explicitly.
Traits
Trait is default
An attribute that is assigned Nil will revert to its default value
set with the trait is default
. In the case of arrays or associatives, the
argument of is default
will set the default item value or hash value.
class C {
has $.a is default(42) is rw = 666
}
my $c = C.new;
say $c;
$c.a = Nil;
say $c;
# OUTPUT: «C.new(a => 666)C.new(a => 42)»
class Foo {
has @.bar is default(42) is rw
};
my $foo = Foo.new( bar => <a b c> );
$foo.bar =Nil;
say $foo; # OUTPUT: «Foo.new(bar => [42])»
Trait is required
multi trait_mod:<is> (Attribute $attr, :$required!)
The trait is required
will mark the attribute as to be filled with a value
when the object is instantiated. Failing to do so will result in a runtime
error.
class C {
has $.a is required
}
my $c = C.new;
CATCH{ default { say .^name, ': ', .Str } }
# OUTPUT: «X::Attribute::Required: The attribute '$!a' is required, but you did not provide a value for it.»
This trait also allows attributes to be typed with types that have a
:D
smiley without giving them a default value:
class Power {
has Numeric:D $.base is required;
has Numeric:D $.exponent is required;
multi method Numeric(::?CLASS:D: --> Numeric:D) {
$!base ** $!exponent
}
}
Available as of 6.d language version (early implementation exists in Rakudo compiler 2018.08+): You can specify a reason why the attribute is required:
class D {
has $.a is required("it is a good idea");
}
my $d = D.new;
CATCH{ default { say .^name, ': ', .Str } }
# OUTPUT: «X::Attribute::Required: The attribute '$!a' is required because it is a good idea,but you did not provide a value for it.»
is required
doesn't just affect the default constructor, it checks for the
attribute at a lower level, so it will work for custom constructors written
using bless.
trait is DEPRECATED
multi trait_mod:<is>(Attribute:D $r, :$DEPRECATED!)
Marks an attribute as deprecated, optionally with a message what to use instead.
class C {
has $.foo is DEPRECATED("'bar'");
}
my $c = C.new( foo => 42 ); # doesn't trigger with initialization (yet)
say $c.foo; # does trigger on usage
After the program is finished, this will show something like this on STDERR:
# Saw 1 occurrence of deprecated code.
# =====================================
# Method foo (from C) seen at:
# script.raku, line 5
# Please use 'bar' instead.
trait is rw
multi trait_mod:<is> (Attribute:D $attr, :$rw!)
Marks an attribute as read/write as opposed to the default readonly
. The
default accessor for the attribute will return a writable value.
class Boo {
has $.bar is rw;
has $.baz;
};
my $boo = Boo.new;
$boo.bar = 42; # works
$boo.baz = 42;
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::Assignment::RO: Cannot modify an immutable Any»
trait is built
multi trait_mod:<is>(Attribute:D $a, :$built!)
By default, this trait allows setting up a private attribute during object
construction via .new
. The same trait can be used to prevent setting up a
public attribute via .new
by passing it the Boolean value False
.
class Foo {
has $!bar is built; # same as `is built(True)`
has $.baz is built(False);
method bar {
$!bar
}
}
my $foo = Foo.new(bar => 1, baz => 2);
say $foo.bar; # OUTPUT: «1»
say $foo.baz; # OUTPUT: «Any»
Available as of the 2020.01 release of the Rakudo compiler.
Methods
The usual way to obtain an object of type Attribute
is by introspection:
class Useless {
has @!things;
}
my $a = Useless.^attributes(:local)[0];
say $a.raku; # OUTPUT: «Attribute.new»
say $a.name; # OUTPUT: «@!things»
say $a.package; # OUTPUT: «(Useless)»
say $a.has_accessor; # OUTPUT: «False»
Modifying a private attribute from the outside is usually not possible, but since Attribute is at the level of the metaclass, all is fair game.
method name
method name(Attribute:D: --> Str:D)
Returns the name of the attribute. Note that this is always the private name,
so if an attribute is declared as has $.a
, the name returned is $!a
.
class Foo {
has @!bar;
}
my $a = Foo.^attributes(:local)[0];
say $a.name; # OUTPUT: «@!bar»
method package
method package()
Returns the package (class/grammar/role) to which this attribute belongs.
class Boo {
has @!baz;
}
my $a = Boo.^attributes(:local)[0];
say $a.package; # OUTPUT: «(Boo)»
method has_accessor
method has_accessor(Attribute:D: --> Bool:D)
Returns True
if the attribute has a public accessor method.
class Container {
has $!private;
has $.public;
}
my $private = Container.^attributes(:local)[0];
my $public = Container.^attributes(:local)[1];
say $private.has_accessor; # OUTPUT: «False»
say $public.has_accessor; # OUTPUT: «True»
method rw
method rw(Attribute:D: --> Bool:D)
Returns True
for attributes that have the "is rw" trait applied to them.
class Library {
has $.address; # Read-only value
has @.new-books is rw;
}
my $addr = Library.^attributes(:local)[0];
my $new-books = Library.^attributes(:local)[1];
say $addr.rw; # OUTPUT: «False»
say $new-books.rw; # OUTPUT: «True»
method readonly
method readonly(Attribute:D: --> Bool:D)
Returns True
for readonly attributes, which is the default, or False
for
attributes marked as is rw
.
class Library {
has $.address; # Read-only value
has @.new-books is rw;
}
my $addr = Library.^attributes(:local)[0];
my $new-books = Library.^attributes(:local)[1];
say $addr.readonly; # OUTPUT: «True»
say $new-books.readonly; # OUTPUT: «False»
method required
method required(Attribute:D: --> Any:D)
Returns 1
for attributes that have the "is required" trait applied, or Mu
if the attribute did not have that trait applied. If the "is required" trait
is applied with a string, then that string will be returned instead of 1
.
class Library {
has $.address is required;
has @.new-books is required("we always need more books");
}
my $addr = Library.^attributes(:local)[0];
my $new-books = Library.^attributes(:local)[1];
say $addr.required; # OUTPUT: «1»
say $new-books.readonly; # OUTPUT: «"we always need more books"»
method type
method type(Attribute:D: --> Mu)
Returns the type constraint of the attribute.
class TypeHouse {
has Int @.array;
has $!scalar;
has @.mystery;
}
my @types = TypeHouse.^attributes(:local)[0..2];
for 0..2 { say @types[$_].type }
# OUTPUT: «(Positional[Int])
# (Mu)
# (Positional)»
method get_value
method get_value(Mu $obj)
Returns the value stored in this attribute of object $obj
.
class Violated {
has $!private-thing = 5;
}
my $private = Violated.^attributes(:local)[0];
say $private.get_value(Violated.new); # OUTPUT: «5»
Note that this method violates encapsulation of the object, and should be used with care. Here be dragons.
method set_value
method set_value(Mu $obj, Mu \new_val)
Binds the value new_val
to this attribute of object $obj
.
class A {
has $!a = 5;
method speak() { say $!a; }
}
my $attr = A.^attributes(:local)[0];
my $a = A.new;
$a.speak; # OUTPUT: «5»
$attr.set_value($a, 42);
$a.speak; # OUTPUT: «42»
Note that this method violates encapsulation of the object, and should be used with care. Here be dragons.
method gist
multi method gist(Attribute:D:)
Returns the name of the type followed by the name of the attribute.
class Hero {
has @!inventory;
has Str $.name;
submethod BUILD( :$name, :@inventory ) {
$!name = $name;
@!inventory = @inventory
}
}
say Hero.^attributes(:local)[0]; # OUTPUT: «Positional @!inventory»
Since say implicitly calls .gist
, that is what produces the output here.
Optional introspection
DEPRECATED
If an attribute is marked as DEPRECATED
, then calling the DEPRECATED
method is possible and will return "something else"
(if no specific reason
was specified) or the string that was specified with the DEPRECATED
trait.
If an attribute is not marked as DEPRECATED, one cannot call the
DEPRECATED
method. Therefore, the .?method
syntax should be used.
class Hangout {
has $.table;
has $.bar is DEPRECATED("the patio");
}
my $attr-table = Hangout.^attributes(:local)[0];
my $attr-bar = Hangout.^attributes(:local)[1];
with $attr-table.?DEPRECATED -> $text { # does not trigger
say "Table is deprecated with '$text'";
# OUTPUT:
}
with $attr-bar.?DEPRECATED -> $text {
say "Bar is deprecated with '$text'";
# OUTPUT: «Bar is deprecated with 'the patio'"»
}