Phil Crow wrote:
> class Die;
>
> has Int $!sides;
>
> method new( Int $sides = 6 ) {
> return self.bless( *, :$sides );
> }
>
> method roll() returns Int {
> return round 1 + rand * $!sides;
> }
Just as a side note, this is could be easier written as
class Die;
has Int $!sides = 6;
method roll() { return round 1 + rand * $!sides}
There is automatically a constructor available that lets you write
Die.new(sides => 20)
if you want to deviate from the default.
> I copied the idea for this from the excellent beginner's guide PDF in the
> Rakudo star distributions. Here is my first question: What exactly are
> the arguments of bless?
If you know Perl 5, you'll know that its bless function takes a
reference as first argument, which is then turned into an object.
That's also the case in Perl 6. The * means "create an appropriate
candidate for me". In theory you should be able to pass a hash or an
array as first argument to bless too, but I doubt that's implemented in
Rakudo.
The named arguments to bless() are the initial values of the attributes
of the new object.
> Further, why does the sides attribute
> need a leading colon (or does it)?
It's a named argument. See for example
http://github.com/downloads/perl6/book/2010.08.letter.pdf page 36
(logical page number), section 4.2.5 Named Parameters.
> This works fine as is. I have only one question here: Why do I need
> to change from $.was-doubles to $!was-doubles when I access the attribute
> in instance methods? When I tried to use $.was-doubles, the compiler
> threw: Cannot modify readonly value. I'm assuming this is the correct
> behavior, but I'd like a bit of clarification.
I fear this is becoming an FAQ.
The reason is that even if you write 'has $.attrib' in your class, the
attribute is actually called $!attrib, and additionally a method of the
same name is created for you, which is a read-only accessors.
When you write $.attr in ordinary code (ie not in 'has'), it means the
same thing as (self.attr). So, long story short, $.attr is a method
call, to which you can't assign, unless you take special precautions
(like writing has $.attr is rw;).
> Finally, I've come to my problem. Recall that the Die class provides
> a default number of six sides. Suppose I replace the Die-Pair
> constructor call with this:
>
> my Die-Pair $die-pair = Die-Pair.new();
>
> I want to receive the default from the Die constructor for each instance
> I construct. But the output tells me this did not work:
>
> 0 Roll! 2 was doubles? 1
> 1 Roll! 2 was doubles? 1
> 2 Roll! 2 was doubles? 1
> 3 Roll! 2 was doubles? 1
> ...
>
> What must I do so that my aggregating class can allow the caller to
> use the defaults of the aggregated class? My current work around is
> to use multi constructors:
>
> multi method new( Int $sides1, Int $sides2 ) {
> my Die $die1 = Die.new( $sides1 );
> my Die $die2 = Die.new( $sides2 );
> return self.bless( *, :$die1, :$die2 );
> }
>
> multi method new() {
> my Die $die1 = Die.new();
> my Die $die2 = Die.new();
> return self.bless( *, :$die1, :$die2 );
> }
>
> This is what I would need to do in Java. But, I wanted to make the point
> that defaults could eliminate the need for this type of overloaded
> constructor
> in Perl 6.
What about
method new (Int $sides1? Int $sides2?) {
my Die $die1 = defined $sides1 ?? Die.new( $sides1 ) !! Die.new;
my Die $die2 = defined $sides2 ?? Die.new( $sides2 ) !! Die.new;
return self.bless( *, :$die1, :$die2 );
}
It's not ideal, but I think it's a workable solution.
If you want to generalize to a list of dice, you might want to do
something along these lines:
method new(Int $count, *...@defaults) {
my @dice = @defaults.map: { Die.new($_) };
# populate all dice for which no number was provided:
for ^($count - @defaults) {
@dice.push: Die.new();
}
}
There are other solutions I can think of that avoid checking for
definedness of arguments, but they all involve some trickery.
Maybe it becomes cleaner if we move the defaults to the attribute
declaration:
has $!die1 = Die.new;
has $!die2 = Die.new;
method new (Int $sides1? Int $sides2?) {
my %attributes;
%attributes<die1> = Die.new($sides1) if defined $sides1;
%attributes<die2> = Die.new($sides2) if defined $sides2;
# use the hash as list of named arguments
return self.bless( *, |%attributes );
}
I haven't tested if this works in Rakudo today, but I think in theory it
should. The default attribute values should only be used if no value for
that attribute is passed to bless.
Hope this helps a bit,
Moritz