in reply to Re: Raku: Function Signatures
in thread Raku: Function Signatures

Thanks for your answer. It helped me make progress in understanding the syntax. The following is what I figured out with your help.

In Raku, you can specify a type for a parameter variable:

sub echo(Int $x) { say $x.^name; say "echo() received $x"; } echo(3); --output:-- Int echo() received 3

However, specifying the type Int will allow you to call the function with both integers and the type object itself Int

sub echo(Int $x) { say $x.^name; } echo(Int); --output:-- Int

The syntax Int:D means you can call the function with instances of the Int class, but not the Int type object:

sub echo(Int:D $x) { say $x.^name; } echo(Int); --output:-- Parameter '$x' of routine 'echo' must be an object instance of type 'Int', not a type object of type 'Int'. Did you forget a '.new'? in sub echo at b.raku line 14 in block <unit> at b.raku line 18

Similarly, you can disallow instances with the syntax Int:U, which only allows you to pass the Int type object:

sub echo(Int:U $x) { say $x.^name; } echo(3); --output:-- Parameter '$x' of routine 'echo' must be a type object of type 'Int', not an object instance of type 'Int'. Did you forget a 'multi'? in sub echo at b.raku line 14 in block <unit> at b.raku line 22

I think the hint in the error message is asking whether you meant to do:

multi sub echo(Int:U $x) { say $x.^name; } multi sub echo(Int:D $x) { say $x.^name; } echo(3);

Next, I tried the syntax Int:D: to see what would happen:

sub echo(Int:D: $x) { say $x.^name; } echo(3); --output:-- ===SORRY!=== Error while compiling /Users/7stud/raku_programs/b.raku Can only use the : invocant marker in the signature for a method at /Users/7stud/raku_programs/b.raku:14 ------> sub echo(Int:D: $x&#9167;) { expecting any of: constraint

Then I played around with classes and methods, and this is what I came up with:

class Dog { has Str $.name; method bark(Int:D $x) { say self.^name; # self is an implicit variable that refers t +o the invocant say "bark" for 1..$x; } } my $d = Dog.new(name => "Rover"); $d.bark(3); ---output:-- Dog bark bark bark

And:

class Dog { has Str $.name; method bark($dog: Int:D $x) { say $dog.^name; # $dog is an explicit variable that refers t +o the invocant say $dog.name; say "bark" for 1..$x; } } my $d = Dog.new(name => "Rover"); $d.bark(3); --output:-- Dog Rover bark bark bark
In Operators, the colon is listed as a comma. It does act like a separator between the two parameter variables in bark(), but the colon does more than that. In Parameter Separators the docs call the colon an invocant marker, and the docs say:

...the first parameter may be followed by a colon instead of a comma to mark the invocant of a method. This is done in order to distinguish it from what would then be a regular positional parameter. The invocant is the object that was used to call the method, which is usually bound to self. By specifying it in the signature, you can change the variable name it is bound to.

I was unable to actually write something like Dog:D: in my code. Trying different ways of specifying Dog:D: caused errors, for instance the following didn't work:

class Dog { has Str $.name; method bark(Dog:D:dog: Int $x) { say $dog.^name; say $dog.name; say "bark" for 1..$x; } }

Okay, I was able to use the syntax Dog:D: in my code:

class Dog { has Str $.name; method bark(Dog:D: Int:D $x) { say self.^name; say "bark" for 1..$x; } } my $d = Dog.new(name => "Rover"); $d.bark(3);

Specifying Dog:D: means that the invocant of the method (trailing :) has to be a Dog instance (Dog:D). This won't work:

class Dog { has Str $.name; method bark(Dog:D: Int:D $x) { say self.^name; say "bark" for 1..$x; } } Dog.bark(3); --output:-- Invocant of method 'bark' must be an object instance of type 'Dog', no +t a type object of type 'Dog'. Did you forget a '.new'? in method bark at b.raku line 30 in block <unit> at b.raku line 36

Without the Dog:D:

class Dog { has Str $.name; method bark(Int:D $x) { say self.^name; say "bark" for 1..$x; } } Dog.bark(3); --output:-- Dog bark bark bark

As a result, it looks like you can specify Dog:D: to limit method calls to instances, but you can't use the syntax Dog:D: to change the name of self.

Ahh. Here is the syntax which both limits the method call to instances of the class AND changes the name of self:

class Dog { has Str $.name; method bark(Dog:D $dog: Int:D $x) { say $dog.^name; say $dog.name; say "bark" for 1..$x; } } my $d = Dog.new(name => 'Rover'); $d.bark(3); Dog.bark(3); --output:-- Dog Rover bark bark bark Invocant of method 'bark' must be an object instance of type 'Dog', no +t a type object of type 'Dog'. Did you forget a '.new'? in method bark at b.raku line 30 in block <unit> at b.raku line 39

Therefore, when you read something like the following in the docs:

routine chr multi sub chr(Int:D --> Str:D) multi method chr(Int:D: --> Str:D)

The first line describes a sub/function named chr() that takes one argument of type Int:D, which means an instance of the Int class. In rakudo (the REPL for raku):

32] > chr(99); c

The second line for chr() describes a method, whose invocant (trailing colon) must be an instance of the Int class (Int:D). The syntax Int:D: does NOT specify a positional parameter (even if followed by a variable name!), so the method takes no arguments. In rakudo:

[33] > 99.chr c

The second example on the page class Method uses Int:D: in some code:

my method m(Int:D: $b){ say self.^name } my $i = 1; $i.&m(<a>); # OUTPUT: «Int»
Apparently, you can have a free floating method that is not part of a class, and you call it with .&. Then, as described above, Int:D: means the invocant (:) has to be of type Int:D, i.e. an instance of the Int class. And <a> is the way you "quote words", so the argument is 'a', which gets assigned to the parameter variable $b. That example seems to suggest that you can add functionality to any class with a free floating method that specifies the type of the invocant to be of that class.

Cha-ching

Replies are listed 'Best First'.
Re^3: Raku: Function Signatures
by smokemachine (Hermit) on Feb 19, 2024 at 18:34 UTC
    You could also do method bark(Dog:D \dog: Int:D $x) {

      That seems like it might be a "good practice"? Renaming self with a constant, sigil-less variable will prevent you from losing your self?

      Well, on the other hand, it looks like $dog is readonly:

      lass Dog { has Str $.name; method bark(Dog:D $dog: Int:D $x) { say $dog.^name; say $dog.name; $dog = 'hello'; say "bark" for 1..$x; } } Dog.new.bark(3); --output:-- Dog (Str) Cannot assign to an immutable value in method bark at b.raku line 19 in block <unit> at b.raku line 24

      So is using a sigil-less variable to capture self more of a style thing? For instance, to make you notice that the variable is different from other variables?