Reputation: 12114
Suppose I have the following code:
my constant @suits = <Clubs Hearts Spades Diamonds>;
my constant @values = 2..14;
class Card {
has $.suit;
has $.value;
# order is mnemonic of "$value of $suit", i.e. "3 of Clubs"
multi method new($value, $suit) {
return self.bless(:$suit, :$value);
}
}
It defines some suits and some values and what it means to be a card.
Now, to build a deck, I essentially need to take the cross product of the suits and the values and apply that to the constructor.
The naiive approach to do this, would of course be to just iterate with a loop:
my @deck = gather for @values X @suits -> ($v, $c) {
take Card.new($v, $c);
}
But this is Raku, we have a cross
function that can take a function as an optional argument!, so of course I'm gonna do that!
my @deck = cross(@values, @suits, :with(Card.new));
# Unexpected named argument 'with' passed
# in block <unit> at .\example.raku line 36
... wait no. What about this?
my @deck = cross(@values, @suits):with(Card.new);
# Unexpected named argument 'with' passed
# in block <unit> at .\example.raku line 36
Still nothing. Reference maybe?
my @deck = cross(@values, @suits):with(&Card.new);
# ===SORRY!=== Error while compiling D:\Code\Raku/.\example.raku
# Illegally post-declared type:
# Card used at line 36
I read somewhere I can turn a function into an infix operator with []
my @deck = cross(@values, @suits):with([Card.new]);
# Unexpected named argument 'with' passed
# in block <unit> at .\example.raku line 36
That also doesn't work.
If classes are supposed to just be modules, shouldn't I then be able to pass a function reference?
Also why is it saying 'with' is that's unexpected? If I'm intuiting this right, what it's actually complaining about is the type of the input, rather than the named argument.
Upvotes: 6
Views: 187
Reputation: 34120
The title of your question is “How do I take a reference to new?”, but that is not really what you want to do.
Raku being Raku, you can actually get a reference to new
.
my $ref = Card.^lookup('new');
You can't use it like you want to though.
$ref(2,'Clubs'); # ERROR
The problem is that methods take a class or instance as the first argument.
$ref(Card, 2,'Clubs');
You could use .assuming
to add it in.
$ref .= assuming(Card);
$ref(2,'Clubs');
But that isn't really any better than creating a block lambda
$ref = { Card.new( |@_ ) }
$ref(2,'Clubs');
All of these work:
cross( @values, @suits ) :with({Card.new(|@_)}) # adverb outside
cross( @values, @suits, :with({Card.new(|@_)}) ) # inside at end
cross( :with({Card.new(|@_)}), @values, @suits ) # inside at beginning
@values X[&( {Card.new(|@_)} )] @suits # cross meta-op with fake infix op
do {
sub new-card ($value,$suit) { Card.new(:$value,:$suit) }
@values X[&new-card] @suits
}
do {
sub with ($value,$suit) { Card.new(:$value,:$suit) }
cross(@values,@suits):&with
}
Upvotes: 4
Reputation: 26924
The error message is indeed confusing.
The :with
parameter expects a Callable. Card.new
is not a Callable. If you write it as :with( { Card.new($^number, $^suit) } )
, it appears to work.
Note that I did not use $^value, $^suit
, because they order differently alphabetically, so would produce the values in the wrong order. See The ^ twigil for more information on that syntax.
The error is LTA, this makes it a little bit better.
To get back to your question: you can find the code object that corresponds to Card.new
with ^find_method
. However, that will not work, as Card.new
actually expects 3 arguments: the invocant (aka self
), $value
and $suit
. Whereas the cross
function will only pass the value and the suit.
Upvotes: 8