I need to pass some code from an external program into a class.
In a generic module I have (for simplicity reduced to silliness)
class A {
has &.hl;
submethod BUILD( :&!hl ) {}
}
Elsewhere in a program, I have
use A;
my &hl = -> $st {
my $p = shell "hl $st", :in,:out;
$p.out.slurp
};
my $statement = 'my $raku-variable = "Helloooo";'
my $first = &hl($statement);
my A $a .= new(:&hl);
my $second = $a.hl( $statement );
$first
will be processed and will contain the expected results.
At $second
, I will get a runtime error
Too many positionals passed; expected 1 argument but got 2
Clearly the routine in the class is being provided both the invocant and the parameter $s
.
Rewriting the class to provide a custom accessor:
class A {
has &!hl;
submethod BUILD( :&!hl ) {}
method process-it( Str $s --> Str ) { &!hl( $s ) }
}
# elsewhere
my $second = $a.process-it( $statement );
Then both $first
and $second
run without error and will contain the same results.
When hl
is accessed inside the class, no invocant is added, but if it is not declared as &.hl
then it is not visible outside the class.
My question is therefore: Is there another way to create a public object code variable that does not automagically add the invocant as a variable to the code? Other than creating a separate accessor method.
Here is short bash script hl
for illustration
#! /bin/bash
echo '<div class="statement">'$1'</div>'
Here is a full Raku program
use v6.c;
class A {
has &!highlighter; # also tried with has &highlighter
submethod BUILD( :&!highlighter ) {}
method process-it( Str $s --> Str ) {
&!highlighter( $s )
}
}
sub MAIN() {
my @strings = 'my $v = "Hello World";', 'my $w = $v.raku;';
my $proc;
my $proc-supply;
my &highlighter = -> $s {
my $p = shell "./hl '$s' ", :in,:out;
$p.out.slurp
}
for @strings {
say .&highlighter
}
my A $a .= new(:&highlighter);
for @strings { say $a.highlighter($_) }
# own accessor
for @strings { say $a.process-it($_) }
}
has $!hl
declares a private attribute. has $.hl
declares a public attribute.
By public I mean it creates a method of the same name that returns it, and it adds it to the BUILD
/gist
/perl
/Capture
[sub]methods.
class A {
has &.hl;
}
This is effectively the same as:
class A {
has &!hl;
submethod BUILD ( :&!hl ){}
method hl (){ &!hl } # return the code object
method perl (){
"A.new(hl => $!hl.perl())"
}
method gist (){ self.perl }
method Capture () {
\( :&!hl )
}
}
So when you call A.hl
it returns the code object that is stored in &!hl
.
You can deal with this in a few ways.
Just call it “twice”.
$a.hl()(42)
$a.hl().(42)
$a.hl.(42)
Have an additional method that uses it.
method call-it ( |C ){
&!hl( |C )
}
$a.call-it( 42 )
my &hl = $a.hl;
Note that I used |C
to avoid dealing with signatures entirely.
It might make sense for you to have a signature and deal with it like you have.
Override the automatically generated method by adding it yourself.
method hl ( |C ){
&!hl( |C )
}
$a.hl( 42 )
By overriding it, all of the other changes that making it a public attribute are still done for you.
So there will be no need to create a BUILD
submethod.
When you override it, that means that is rw
has no effect. It also means that
there is no way for outside code to retrieve the code object itself.
There are ways to deal with that if you need to.
If you don't ever need to return the value in &!hl
then just leave it like it is above.
If the code object is never called with zero positional arguments.
multi method hl (){ &!hl }
multi method hl ( |C ){
&!hl( |C )
}
$a.hl; # returns the value in $!hl
$a.hl(); # returns the value in $!hl
$a.hl( 42 ); # calls &!hl(42)
Note that there is no way for a method to differentiate between .hl
and .hl()
.
You could also use a named argument.
multi method hl ( :code($)! ){ &!hl }
multi method hl ( |C ){
&hl( |C )
}
$a.hl(:code); # returns the value in &!hl
$a.hl; # calls &!hl()
$a.hl(); # calls &!hl()
$a.hl( 42 ); # calls &!hl(42)
You could do nothing to make it easier to get the code object, and just have them use subsignature parsing to get the attribute.
(This is why the Capture
method gets created for you)
class A {
has &.hl;
method hl ( |C ){
&!hl( |C )
}
}
sub get-hl ( A $ ( :&hl ) ){ &hl }
my &hl = get-hl($a);
my &hl = -> A $ ( :&hl ){ &hl }( $a );
my &hl = $a.Capture{'hl'};
TL;DR There is no way to directly access an attribute outside the source code of the class in which it is declared. The only way to provide access is via a separate public accessor method. This answer hopefully clears up confusion about this. Other answers lay out your options.
Too many positionals passed;
error messageThe code has &!hl;
declares an attribute, &!hl
.
The code has &.hl;
does the same but also generates a method, .hl
that's a public accessor to the attribute with the same name. Like all such generated accessors, it expects a single argument, the invocant, and no others.
my $second = $a.hl( $statement )
This code calls the method hl
. Raku passes the value on the left of the dot ($a
) as a first argument -- the invocant. But you've also added a $statement
argument. So it passes that too.
Hence the error message:
Too many positionals passed; expected 1 argument but got 2
When
hl
is accessed inside the class, no invocant is added
It's not because it's accessed inside the class. It's because you don't call it as a method:
method process-it( Str $s --> Str ) { &!hl( $s ) }
The &!hl( $s )
code is a sub style call of the routine held in the &!hl
attribute. It gets one argument, $s
.
Is there another way to create a public object code variable that does not automagically add the invocant as a variable to the code?
The problem is not that Raku is automagically adding an invocant.
Other than creating a separate accessor method.
There is no way to directly access an attribute outside the source code of the class in which it is declared. The only way to provide access is via a separate public accessor method. This answer hopefully clears up confusion about this. Other answers lay out your options.
The problem is that the accessor returns the attribute, that happens to be a Callable
. Only then do you want to call the return value of the accessor with parameters. This is essentially what you're doing by creating your own accessor.
You don't have to actually create your own accessor. Just add a extra parentheses (indicating you're calling the accessor without any extra arguments), and then the parentheses for the values you actually want to pass:
class A {
has &.a = *.say; # quick way to make a Callable: { .say }
}
A.new.a()(42); # 42
Or if you don't like parentheses so much, consider the method invocation syntax, as timotimo pointed out:
A.new.a.(42); # 42
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With