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

Reply via email to