To split e.g. mins-2 into component parts of units name and order, this does what I want
sub split-order ( $string ) {
my Str $i-s = '1';
$string ~~ / ( <-[\-\d]>+ ) ( \-?\d? ) /;
$i-s = "$1" if $1 ne '';
return( "$0", +"$i-s".Int );
}
It seems that perl6 should be able to pack this into a much more concise phrasing. I need default order of 1 where there is no trailing number.
I am probably being a bit lazy not matching the line end with $. Trying to avoid returning Nil as that is not useful to caller.
Anyone with a better turn of phrase?
How about using good old split?
use v6;
sub split-order(Str:D $in) {
my ($name, $qty) = $in.split(/ '-' || <?before \d>/, 2);
return ($name, +($qty || 1));
}
say split-order('mins-2'); # (mins 2)
say split-order('foo42'); # (foo 42)
say split-order('bar'); # (bar 1)
This does not reproduce your algorithm exactly (and in particular doesn't produce negative numbers), but I suspect it's closer to what you actually want to achieve:
sub split-order($_) {
/^ (.*?) [\-(\d+)]? $/;
(~$0, +($1 // 1));
}
Related
The documentation says that the comma operator has higher precedence than the assignment = operator, and this is specifically different than in Perl, so that we are allowed to remove parentheses in some contexts.
This allows us to do things like this:
my #array = 1, 2, 3;
What I don't understand is why when do something like this:
sub test() { return 1, 2 }
my ($a, $b);
$a, $b = test();
$b get assigned [1 2] while $a gets no value.
While I would assume that the following would be equivalent, because the comma operator is tighter than the assignment.
($a, $b) = test();
The semantics of Raku have a lot of subtlety and I guess I am thinking too much in terms of Perl.
Like raiph said in the comments, my original assumption that the comma operator has higher precedence than the assignment operator was false. And it was due to a problem in the rendering of the operator precedence table, which didn't presented operators in their precedence order.
This explains the actual behavior of Raku for my examples.
The = operator itself is always item assignment level, which is tighter than comma. However, it may apply a "sub-precedence", which is how it is compared to any further infixes in the expression that follow it. The term that was parsed prior to the = is considered, and:
In the case that the assignment is to a Scalar variable, then the = operator works just like any other item assignment precedence operator, which is tighter than the precedence of ,
In any other case, its precedence relative to following infixes is list prefix, which is looser than the precedence of ,
To consider some cases (first, where it doesn't impact anything):
$foo = 42; # $ sigil, so item assignment precedence after
#foo = 1; # # sigil, so list assignment precedence after
#foo[0] = 1; # postcircumfix (indexing operator) means list assignment after...
$foo[0] = 1; # ...always, even in this case
If we have a single variable on the left and a list on the right, then:
#foo = 1, 2; # List assignment into #foo two values
$foo = 1, 2; # Assignment of 1 into $foo, 2 is dead code
These apply with the = initializer (following a my $var declaration). This means that:
loop (my $i = 0, my $j = $end; $i < $end; $i++, $j--) {
...
}
Will result in $i being assigned 0 and $j being assigned $end.
Effectively, the rule means we get to have parentheses-free initialization of array and hash variables, while still having lists of scalar initializations work out as in the loop case.
Turning to the examples in the question. First, this:
($a, $b) = test();
Parses a single term, then encounters the =. The precedence when comparing any following infixes would be list prefix (looser than ,). However, there are no more infixes here, so it doesn't really matter.
In this case:
sub test() { return 1, 2 }
my ($a, $b);
$a, $b = test();
The precedence parser sees the , infix, followed by the = infix. The = infix in itself is tighter than comma (item assignment precedence); the sub-precedence is only visible to infixes parsed after the = (and there are none here).
Note that were it not this way, and the precedence shift applied to the expression as a whole, then:
loop (my $i = 0, my #lagged = Nil, |#values; $i < #values; $i++) {
...
}
Would end up grouped not as (my $i = 0), (my #lagged = Nil, |#values), but rather (my $i = 0, my #lagged) = Nil, |#values, which is rather less useful.
In Ruby I can group together some lines of code like so with a begin block:
x = begin
puts "Hi!"
a = 2
b = 3
a + b
end
puts x # 5
it's immediately evaluated and its value is the last value of the block (a + b here) (Javascripters do a similar thing with IIFEs)
What are the ways to do this in Raku? Is there anything smoother than:
my $x = ({
say "Hi!";
my $a = 2;
my $b = 3;
$a + $b;
})();
say $x; # 5
Insert a do in front of the block. This tells Raku to:
Immediately do whatever follows the do on its right hand side;
Return the value to the do's left hand side:
my $x = do {
put "Hi!";
my $a = 2;
my $b = 3;
$a + $b;
}
That said, one rarely needs to use do.
Instead, there are many other IIFE forms in Raku that just work naturally without fuss. I'll mention just two because they're used extensively in Raku code:
with whatever { .foo } else { .bar }
You might think I'm being silly, but those are two IIFEs. They form lexical scopes, have parameter lists, bind from arguments, the works. Loads of Raku constructs work like that.
In the above case where I haven't written an explicit parameter list, this isn't obvious. The fact that .foo is called on whatever if whatever is defined, and .bar is called on it if it isn't, is both implicit and due to the particular IIFE calling behavior of with.
See also if, while, given, and many, many more.
What's going on becomes more obvious if you introduce an explicit parameter list with ->:
for whatever -> $a, $b { say $a + $b }
That iterates whatever, binding two consecutive elements from it to $a and $b, until whatever is empty. If it has an odd number of elements, one might write:
for whatever -> $a, $b? { say $a + $b }
And so on.
Bottom line: a huge number of occurrences of {...} in Raku are IIFEs, even if they don't look like it. But if they're immediately after an =, Raku defaults to assuming you want to assign the lambda rather than immediately executing it, so you need to insert a do in that particular case.
Welcome to Raku!
my $x = BEGIN {
say "Hi!";
my $a = 2;
my $b = 3;
$a + $b;
}
I guess the common ancestry of Raku and Ruby shows :-)
Also note that to create a constant, you can also use constant:
my constant $x = do {
say "Hi!";
my $a = 2;
my $b = 3;
$a + $b;
}
If you can have a single statement, you can leave off the braces:
my $x = BEGIN 2 + 3;
or:
my constant $x = 2 + 3;
Regarding blocks: if they are in sink context (similar to "void" context in some languages), then they will execute just like that:
{
say "Executing block";
}
No need to explicitely call it: it will be called for you :-)
Identifier terms are defined in the documentation alongside constants, with pretty much the same use case, although terms compute their value in run time while constants get it in compile time. Potentially, that could make terms use global variables, but that's action at a distance and ugly, so I guess that's not their use case.
OTOH, they could be simply routines with null signature:
sub term:<þor> { "Is mighty" }
sub Þor { "Is mighty" }
say þor, Þor;
But you can already define routines with null signature. You can sabe, however, the error when you write:
say Þor ~ Þor;
Which would produce a many positionals passed; expected 0 arguments but got 1, unlike the term. That seems however a bit farfetched and you can save the trouble by just adding () at the end.
Another possible use case is defying the rules of normal identifiers
sub term:<✔> { True }
say ✔; # True
Are there any other use cases I have missed?
Making zero-argument subs work as terms will break the possibility to post-declare subs, since finding a sub after having parsed usages of it would require re-parsing of earlier code (which the perl 6 language refuses to do, "one-pass parsing" and all that) if the sub takes no arguments.
Terms are useful in combination with the ternary operator:
$ perl6 -e 'sub a() { "foo" }; say 42 ?? a !! 666'
===SORRY!=== Error while compiling -e
Your !! was gobbled by the expression in the middle; please parenthesize
$ perl6 -e 'sub term:<a> { "foo" }; say 42 ?? a !! 666'
foo
Constants are basically terms. So of course they are grouped together.
constant foo = 12;
say foo;
constant term:<bar> = 36;
say bar;
There is a slight difference because term:<…> works by modifying the parser. So it takes precedence.
constant fubar = 38;
constant term:<fubar> = 45;
say fubar; # 45
The above will print 45 regardless of which constant definition comes first.
Since term:<…> takes precedence the only way to get at the other value is to use ::<fubar> to directly access the symbol table.
say ::<fubar>; # 38
say ::<term:<fubar>>; # 45
There are two main use-cases for term:<…>.
One is to get a subroutine to be parsed similarly to a constant or sigilless variable.
sub fubar () { 'fubar'.comb.roll }
# say( fubar( prefix:<~>( 4 ) ) );
say fubar ~ 4; # ERROR
sub term:<fubar> () { 'fubar'.comb.roll }
# say( infix:<~>( fubar, 4 ) );
say fubar ~ 4;
The other is to have a constant or sigiless variable be something other than an a normal identifier.
my \✔ = True; # ERROR: Malformed my
my \term:<✔> = True;
say ✔;
Of course both use-cases can be combined.
sub term:<✔> () { True }
Perl 5 allows subroutines to have an empty prototype (different than a signature) which will alter how it gets parsed. The main purpose of prototypes in Perl 5 is to alter how the code gets parsed.
use v5;
sub fubar () { ord [split('','fubar')]->[rand 5] }
# say( fubar() + 4 );
say fubar + 4; # infix +
use v5;
sub fubar { ord [split('','fubar')]->[rand 5] }
# say( fubar( +4 ) );
say fubar + 4; # prefix +
Perl 6 doesn't use signatures the way Perl 5 uses prototypes. The main way to alter how Perl 6 parses code is by using the namespace.
use v6;
sub fubar ( $_ ) { .comb.roll }
sub term:<fubar> () { 'fubar'.comb.roll }
say fubar( 'zoo' ); # `z` or `o` (`o` is twice as likely)
say fubar; # `f` or `u` or `b` or `a` or `r`
sub prefix:<✔> ( $_ ) { "checked $_" }
say ✔ 'under the bed'; # checked under the bed
Note that Perl 5 doesn't really have constants, they are just subroutines with an empty prototype.
use v5;
use constant foo => 12;
use v5;
sub foo () { 12 } # ditto
(This became less true after 5.16)
As far as I know all of the other uses of prototypes have been superseded by design decisions in Perl 6.
use v5;
sub foo (&$) { $_[0]->($_[1]) }
say foo { 100 + $_[0] } 5; # 105;
That block is seen as a sub lambda because of the prototype of the foo subroutine.
use v6;
# sub foo ( &f, $v ) { f $v }
sub foo { #_[0].( #_[1] ) }
say foo { 100 + #_[0] }, 5; # 105
In Perl 6 a block is seen as a lambda if a term is expected. So there is no need to alter the parser with a feature like a prototype.
You are asking for exactly one use of prototypes to be brought back even though there is already a feature that covers that use-case.
Doing so would be a special-case. Part of the design ethos of Perl 6 is to limit the number of special-cases.
Other versions of Perl had a wide variety of special-cases, and it isn't always easy to remember them all.
Don't get me wrong; the special-cases in Perl 5 are useful, but Perl 6 has for the most part made them general-cases.
I'm playing around with a positional interface for strings. I'm aware of How can I slice a string like Python does in Perl 6?, but I was curious if I could make this thing work just for giggles.
I came up with this example. Reading positions is fine, but I don't know how to set up the multi to handle an assignment:
multi postcircumfix:<[ ]> ( Str:D $s, Int:D $n --> Str ) {
$s.substr: $n, 1
}
multi postcircumfix:<[ ]> ( Str:D $s, Range:D $r --> Str ) {
$s.substr: $r.min, $r.max - $r.min + 1
}
multi postcircumfix:<[ ]> ( Str:D $s, List:D $i --> List ) {
map( { $s.substr: $_, 1 }, #$i ).list
}
multi postcircumfix:<[ ]> ( Str:D $s, Int:D $n, *#a --> Str ) is rw {
put "Calling rw version";
}
my $string = 'The quick, purple butterfly';
{ # Works
my $single = $string[0];
say $single;
}
{ # Works
my $substring = $string[5..9];
say $substring;
}
{ # Works
my $substring = $string[1,3,5,7];
say $substring;
}
{ # NOPE!
$string[2] = 'Perl';
say $string;
}
The last one doesn't work:
T
uick,
(h u c)
Index out of range. Is: 2, should be in 0..0
in block <unit> at substring.p6 line 36
Actually thrown at:
in block <unit> at substring.p6 line 36
I didn't think it would work, though. I don't know what signature or traits it should have to do what I want to do.
Why does the [] operator work on a Str?
$ perl6
> "some string"[0]
some string
The docs mostly imply that the [] works on things that do the Positional roles and that those things are in list like things. From the [] docs in operators:
Universal interface for positional access to zero or more elements of a #container, a.k.a. "array indexing operator".
But a Str surprisingly does the necessary role even though it's not an #container (as far as I know):
> "some string".does( 'Positional' )
True
Is there a way to test that something is an #container?
Is there a way to get something to list all of its roles?
Now, knowing that a string can respond to the [], how can I figure out what signature will match that? I want to know the right signature to use to define my own version to write to this string through [].
One way to achieve this, is by augmenting the Str class, since you really only need to override the AT-POS method (which Str normally inherits from Any):
use MONKEY;
augment class Str {
method AT-POS($a) {
self.substr($a,1);
}
}
say "abcde"[3]; # d
say "abcde"[^3]; # (a b c)
More information can be found here: https://docs.raku.org/language/subscripts#Methods_to_implement_for_positional_subscripting
To make your rw version work correctly, you first need to make the Str which might get mutated also rw, and it needs to return something which in turn is also rw. For the specific case of strings, you could simply do:
multi postcircumfix:<[ ]> ( Str:D $s is rw, Int:D $i --> Str ) is rw {
return $s.substr-rw: $i, 1;
}
Quite often, you'll want an rw subroutine to return an instance of Proxy:
multi postcircumfix:<[ ]> ( Str:D $s is rw, Int:D $i --> Str ) is rw {
Proxy.new: FETCH => sub { $s.substr: $i },
STORE => sub -> $newval { $s.substr-rw( $i, 1 ) = $newval }
}
Although I haven't yet seen production code which uses it, there is also a return-rw operator, which you'll occasionally need instead of return.
sub identity( $x is rw ) is rw { return-rw $x }
identity( my $y ) = 42; # Works, $y is 42.
sub identity-fail( $x is rw ) is rw { return $x }
identity-fail( my $z ) = 42; # Fails: "Cannot assign to a readonly variable or a value"
If a function reaches the end without executing a return, return-rw or throwing an exception, the value of the last statement is returned, and (at present), this behaves as if it were preceded return-rw.
sub identity2( $x is rw ) is rw { $x }
identity2( my $w ) = 42; # Works, $w is 42.
There's a module that aims to let you do this:
https://github.com/zoffixznet/perl6-Pythonic-Str
However:
This module does not provide Str.AT-POS or make Str type do Positional or Iterable roles. The latter causes all sorts of fallout with core and non-core code due to inherent assumptions that Str type does not do those roles. What this means in plain English is you can only index your strings with [...] postcircumfix operator and can't willy-nilly treat them as lists of characters—simply call .comb if you need that.`
Why do I get this error-message?
#!perl6
use v6;
my #a = 1..3;
my #b = 7..10;
my #c = 'a'..'d';
for zip(#a;#b;#c) -> $nth_a, $nth_b, $nth_c { ... };
# Output:
# ===SORRY!===
# Unable to parse postcircumfix:sym<( )>, couldn't find final ')' at line 9
Rakudo doesn't implement the lol ("list of lists") form yet, and so cannot parse #a;#b;#c. For the same reason, zip doesn't have a form which takes three lists yet. Clearly the error message is less than awesome.
There isn't really a good workaround yet, but here's something that will get the job done:
sub zip3(#a, #b, #c) {
my $a-list = flat(#a.list);
my $b-list = flat(#b.list);
my $c-list = flat(#c.list);
my ($a, $b, $c);
gather while ?$a-list && ?$b-list && ?$c-list {
$a = $a-list.shift unless $a-list[0] ~~ ::Whatever;
$b = $b-list.shift unless $b-list[0] ~~ ::Whatever;
$c = $c-list.shift unless $c-list[0] ~~ ::Whatever;
take ($a, $b, $c);
}
}
for zip3(#a,#b,#c) -> $nth_a, $nth_b, $nth_c {
say $nth_a ~ $nth_b ~ $nth_c;
}
The multi-dimensional syntax (the use of ; inside parens) and zip across more than two lists both work, so the code originally posted now works (if you provide some real code rather than the { ... } stub block).