Correctly passing a routine into an object variable

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.

  1. Just call it “twice”.

    $a.hl()(42)
    $a.hl().(42)
    $a.hl.(42)
    
  2. 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.

  3. 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.

  1. 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().

  2. 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)
    
  3. 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.

Why you get a Too many positionals passed; error message

The 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

Tags:

Class

Raku