📜 ⬆️ ⬇️

Perl 6 and Rakudo: Notes from 2009

A series of articles on Perl 6 and Rakudo, one of the compilers that support the Perl6 specification. This article is compiled from 2009 notes.

Install Rakudo


Currently, there are several incomplete implementations of Perl 6. The most complete of them is the Rakudo compiler ( download ).

Git users can create their own copy using the following commands:
$ git clone git://github.com/rakudo/rakudo.git $ cd rakudo $ perl Configure.pl --gen-parrot --gen-moar --gen-nqp --backends=parrot,jvm,moar $ make $ make install 

')
Alternatively, you can build it from source by downloading it from github.com/rakudo/rakudo/tree/nom

For Windows, there is a ready-made binary installer. Installer versions and source code are available by reference.

By executing the perl6 command, you will be taken to the REPL environment, where you can play with various language commands.

 $ perl6 > say "Hello world!"; Hello world! > say (10/7).WHAT (Rat) > say [+] (1..999).grep( { $_ % 3 == 0 || $_ % 5 == 0 } ); 233168 


Lines beginning with “>” are commands, and all others are system responses. The first example is a simple “say” instruction. The second creates a rational number and asks for its type (Rat). The third one takes a list of numbers from 1 to 999, filters those that are not divisible by 3 or 5, adds them and displays the result.

The beauty of formatting


In this article, we will look at the .fmt method.

If you are familiar with the sprintf instruction, then it will be easier for you to deal with .fmt. If not, or if you forgot how to use it - read perldoc. But do not go deep, just browse.

So .fmt. Here are some ways to use it to format strings and integers.

  say 42.fmt('%+d') # '+42' say 42.fmt('%4d') # ' 42' say 42.fmt('%04d') # '0042' say :16<1337f00d>.fmt('%X') # '1337F00D' 


Good, but for now this is just a shorter way to write sprintf. However, when used with arrays (more precisely, lists), it turns out that this method works differently:

  say <  >.fmt #    say <10 11 12>.fmt('%x') # 'abc' say <1 2 3>.fmt('%02d', '; ') # '01; 02; 03' 


But its use with hashes (mappings):

  say { foo => 1, bar => 2 }.fmt # 'foo 1 # bar 2' say { '' => 85, '' => 75 }.fmt('%s   %d ') # '   85  #    75  ' say { '' => 1, '' => 2, '' => 3 }.fmt('%s', ' -- ') #  --  --  


However, in the case of a hash, the order of issuance may differ from that given. For couples, there is also a .fmt, but it works just like hashes. .fmt is a handy tool to change a value or an array of values ​​and bring it to the desired format. It looks like sprintf, but it also works with arrays. The only negative is that the code is too readable. To restore Perl’s reputation as a write-only language, here’s a Christmas present in the form of a one-liner drawing a Christmas tree:

 $ perl6 -e 'say " "x 9-$_,"#"x$_*2-1 for 0..9,2 xx 3' # ### ##### ####### ######### ########### ############# ############### ################# ### ### ### 


Option for Windows (other quotes required):
 > perl6.exe -e "say ' 'x 9-$_,'#'x$_*2-1 for 0..9,2 xx 3" 


Static typing and multi subs


In Perl 5, the $ scalar variables could contain either a reference or a value. The value could be anything - an integer, string, non-integer number, date. Flexibility due to loss in clarity.

Perl 6 introduces static typing. If you need a variable of a certain type, you specify this type during initialization. For example, here is a variable containing an integer:

 my Int $days = 24; 


Other examples of types:

  my Str $phrase = " !"; my Num $pi = 3.141e0; my Rat $other_pi = 22/7; 


To use variables of the old format, you can either not specify the type, or specify the type Any.

The second topic of the chapter is multy subs. This is an opportunity to overload a procedure using the same name for different cases. Here is an example:

 multi sub identify(Int $x) { return "$x –  ."; } multi sub identify(Str $x) { return qq<"$x" –  .>; } multi sub identify(Int $x, Str $y) { return " $x   \"$y\"."; } multi sub identify(Str $x, Int $y) { return " \"$x\"    $y."; } multi sub identify(Int $x, Int $y) { return "   - $x  $y."; } multi sub identify(Str $x, Str $y) { return "  - \"$x\"  \"$y\"."; } say identify(42); say identify("   !"); say identify(42, "    !"); say identify("   !", 42); say identify("   !", "!"); say identify(42, 24); 


Result:

 42 –  . "   !" –  .  42   "    !".  "   !"    42.   - "   !"  "!".    - 42  24. 


Testing


The authors of perl-modules are used to supplying a set of tests with the modules they release to the world. This tradition is maintained in perl 6 through special instructions.

The classic way to write tests in perl is to output data using the Test Anything Protocol. But you don't have to do it manually - you can use the module.

Suppose you have a factorial function:

  sub fac(Int $n) { [*] 1..$n } 


So far it does not matter how it works - we just want to know if it works correctly. Let's check:

 use v6; sub fac(Int $n) { [*] 1..$n } use Test; plan 6; is fac(0), 1, 'fac(0) '; is fac(1), 1, 'fac(1)  '; is fac(2), 2, 'fac(2)  '; is fac(3), 6, 'fac(3)  '; is fac(4), 24, 'fac(4)  '; dies_ok { fac(' ,    ') }, '    '; 


Run:

  $ perl6 fac-test.pl 1..6 ok 1 - fac(0)  ok 2 - fac(1)  ok 3 - fac(2)  ok 4 - fac(3)  ok 5 - fac(4)  ok 6 -      


Details: use Test; loads the testing module, plan 6; announces the launch of six tests. Then there are five lines in the format of "what is", "what we expect to receive", "description". is () compares strings, and since integers are automatically converted to strings, everything works out.

At the end of dies_ok {$ some_code}, $ description, we check that calling a function with a non-integer argument results in an error.

Issuing a test indicates that 6 tests are run, and then on each line displays test results (ok - if passed, not ok - if failed), test number and description.

When you run a large number of tests do not want to view them all in detail, but I want to see the results. The prove command does exactly that:

  prove --exec perl6 fac-test.pl fac-test.pl .. ok All tests successful. Files=1, Tests=6, 11 wallclock secs ( 0.02 usr 0.00 sys + 10.26 cusr 0.17 csys = 10.45 CPU) Result: PASS 


It is customary to add test files to a separate directory t / and run prove recursively on all files from the directory with the .t extension:

  prove --exec perl6 -rt 


If you place this line in the Makefile, then you can simply type make test to run the tests.

Meta-operators


Earlier, we saw an interesting implementation of the factorial function:

  sub fac(Int $n) { [*] 1..$n } 


But how does this work? Perl 6 has several meta-operators that modify existing operators, which are becoming more powerful. Square brackets are a metaoperator, reduce, which places the operator specified inside the brackets between all elements of the list. For example,

  [+] 1, $a, 5, $b 


means the same as

  1 + $a + 5 + $b 


Thus, we can easily summarize the elements of the list:

  $sum = [+] @a; #    @a 


Almost all operators can be placed in square brackets:

  $prod = [*] @a; #   @a $mean = ([+] @a) / @a; #    @a $sorted = [<=] @a; # ,   @a    $min = [min] @a, @b; #       @a  @b 


Therefore, in factorial, the expression [*] 1 .. $ n takes the value of the multiplied list elements from 1 to $ n.

Another meta-operator is hyper. Placing "or" (or their ASCII analogues >> and <<) next to the operator, we make it work on all elements of the list. For example, the following expression makes @c the result of the pairwise addition of the elements @a and @b:

  @c = @a »+« @b; 


In Perl 5, we would have to write something like

  for ($i = 0; $i < @a; $i++) { $c[$i] = $a[$i] + $b[$i]; } 


Hyper is used on various operators, including user-defined operators:

  #    @xyz  1 @xyz»++ #    @x      @a  @b @x = @a »min« @b; 


Instead of arrays, you can use scalars:

  #    @a  3.5 @b = @a »*» 3.5; #    @x  $m   $b @y = @x »*» $m »+» $b; #    @x @inv = 1 «/« @x; #   @last @first   @full @full = (@last »~» ', ') »~« @first; 


Of course, reduce and hyper can be combined:

  #    @x $sumsq = [+] ( @x »**» 2); 


There are many more metaoperators, for example X (cross), R (reverse), S (sequential). Generally speaking, operators like + =, * =, ~ = are already meta-forms of operators, to which an equal sign is added:

  $a += 5; #  ,   $a = $a + 5; $b //= 7; #  ,   $b = $b // 7; $c min= $d; #  ,   $c = $c min $d; 


We leave in the hyperspace

Before we continue exploring metaoperators, we introduce the auxiliary function lsay, which displays nicely formatted lists. Defining it through our you can then use it in the REPL environment:

 our sub lsay(@a) { @a.perl.say } 


Let's start with a simple one: add two lists of the same length:

 > lsay (1, 2, 3, 4) <<+>> (3, 1, 3, 1) [4, 3, 6, 5] > lsay (1, 2, 3, 4) >>+<< (3, 1, 3, 1) [4, 3, 6, 5] 


If the lengths of the lists are the same, both entries are identical. But if their lengths are different:

 > lsay (1, 2, 3, 4) <<+>> (3, 1) [4, 3, 4, 5] > lsay (1, 2, 3, 4) >>+<< (3, 1) #   


The rule is this: what the pointed end of the hyperoperator indicates may be extended if it is shorter than what is on the other end of it. Renewal occurs by repeating the last item in the list. What the “blunt” end indicates is not subject to extension. All combinations are possible, for example, when only the left side (<< + <<), only the right (>> + >>), both sides (<< + >>), or neither side (>> + << ). Single scalars can also be extended:

 > lsay (1, 2, 3, 4) >>+>> 2 [3, 4, 5, 6] > lsay 3 <<+<< (1, 2, 3, 4) [4, 5, 6, 7] 


This is the basics of using hyperoperators. They can also be used with postfix and prefix operators:

 > lsay ~<<(1, 2, 3, 4) ["1", "2", "3", "4"] > my @a= (1, 2, 3, 4); @a>>++; lsay @a; [2, 3, 4, 5] 


It is also possible:

 > lsay (0, pi/4, pi/2, pi, 2*pi)>>.sin [0, 0.707106781186547, 1, 1.22464679914735e-16, -2.44929359829471e-16] > lsay (-1, 0, 3, 42)>>.Str ["-1", "0", "3", "42"] 


In this case, >>. calls a method on each item in the list.

If you want to write array >>. Say, then it is better not to. The use of hyperoperators implies that the operation can be performed in parallel, and the order of operations on the list is not fixed.

Hyper operators work not only with built-in operators. You can define your operator, and they will also work with him. They should work (but do not work yet) with in-place operators - for example, the instruction @a >> / = >> 2 should divide the entire array into 2. They work with multi-dimensional lists, trees and hashes. An interesting example of the use of hyperoperators is the Vector class.
github.com/LastOfTheCarelessMen/Vector/blob/master/lib/Vector.pm
which represents the implementation of multidimensional vectors without a single cycle.

Cycles


Any programmer knows how useful cycles are. A common example of using loops is the foreach loop for traversing arrays. We used this keyword in Perl 5, although it was possible to use for, more reminiscent of style C.

Perl 6 is different.

Now for the passage through the lists used for. foreach is gone, and the word loop is used for C-style. For now, we will only look at for, which is a new, flexible, and powerful language property:

 for 1, 2, 3, 4 { .say } 


Immediately noticeable lack of brackets around the list. Typically, Perl 6 needs fewer brackets than Perl 5. The default variable, as in Perl 5, is $ _. Calling a method without specifying a variable means calling the method $ _, that is, in our case, $ _. Say. You cannot use say with no arguments — you need to write either .say or $ _. Say

Instead of a simple block, you can use a “pointed” block, which allows you to specify the name of a loop variable:

 for 1, 2, 3, 4 -> $i { $i.say } 


The “pointed” block resembles an anonymous procedure, it just does not catch exceptions. And if you write a return inside such a block, it will exit from the whole procedure that caused it. Such blocks take more than one parameter. And what happens if you write this:

 for 1, 2, 3, 4 -> $i, $j { "$i, $j".say } 


When you start you will receive:

 1 2 3 4 


That is, you went through the list, going through two elements at a time. This works with any number of parameters (at least one, and in its absence $ _ is implied). Well, what about creating a list that we follow? Of course, you can use an array variable:

 for @array { .say } 


But in simple cases, we can instead use a map:

 @array.map: *.say; 


Or a hyperoperator, if the sequence is not important to us:

 @array».say; 


But we are not talking about that now. You can create a list through the interval operator <..>:

 for 1..4 { .say } 


It is often necessary to create a list of $ n numbers, starting with 0. One would write 0 .. $ n-1 or use the constructor of spaces 0 .. ^ $ n, but in Perl 6 there is a shorter way using the perfix ^:

 for ^4 { .say } 


At the output we get:

 0 1 2 3 


The reasons for using cycles in the style of C - you need to know which of the list elements we are in now, or we need to go through several arrays at the same time. Perl 6 also has a short entry for this through the Z (zip) operator:

 for @array1 Z @array2 -> $one, $two { ... } 


If both arrays have the same length, $ one passes through all the elements of @ array1, and $ two passes through all the corresponding elements of @ array2. If the length is different, the cycle stops, reaching the end of the shortest. Thus, you can include an array index in a loop like this:

 for ^Inf Z @array -> $index, $item { ... } 


If you don’t like endless lists:

 for ^@array.elems Z @array -> $index, $item { ... } 


which leads to the same result, but the most elegant option is

 for @array.kv -> $index, $item { ... } 


array .kv returns keys and values, and for an array, keys are just indices of elements.

Thus, you can go through at least four arrays at the same time:

 for @one Z @two Z @three Z @four -> $one, $two, $three, $four { ... } 


Limitations of parameters and .comb


As static types limit variable values, so also constraints (constraints) allow regulating the work of procedures and methods. In many PLs, it is necessary to pass parameters to the procedure and check the values ​​obtained. With restrictions, you can check directly during the announcement. Example: we do not need even numbers. In Perl 5, you could write:

 sub very_odd { my $odd = shift; unless ($odd % 2) { return undef; } #     } 


In Perl 6, you can make it easier:

 sub very_odd(Int $odd where {$odd % 2}) { #     } 


If you call very_odd with an even parameter, you get an error. For convenience, you can reload procedures, and work with any numbers:

 multi sub very_odd(Int $odd where {$odd % 2}) { #     } multi sub very_odd(Int $odd) { return Bool::False; } 


Parameter restrictions are conveniently used in pairs with the .comb method. What is .comb? (comb - comb). In the case of hair and a comb, you separate the strands and put them on your head. .comb is the opposite of .split. If the latter method allows you to split a line into those elements that you do not need, then .comb separates it into the necessary elements. Here is a simple example:

 say "Perl 6 Advent".comb(/<alpha>/).join('|'); say "Perl 6 Advent".comb(/<alpha>+/).join('|'); 


The first line gives out “P | e | r | l | A | d | v | e | n | t”: it takes each letter and puts it into a temporary array, which is then combined through “|”. The second line is similar, only there is captured the largest possible number of letters in a row, and the result is “Perl | Advent”.

But .comb is much more powerful. After you have brushed the string, you can manipulate the strands. If you have a string of ASCII characters, you can use hyper operators to replace each piece with an ASCII equivalent:

 say "5065726C36".comb(/<xdigit>**2/)».fmt("0x%s")».chr #  "P erl 6" 


You can write this through the .map method:

 say "5065726C36".comb(/<xdigit>**2/).map: { chr '0x' ~ $_ } ; #  "P erl 6" 


As usual, there is more than one way to do something.

But the task is more complicated: I present to you the ancient cipher of Caesar through the limitations of the parameters, .comb and .map

 use v6; sub rotate_one( Str $c where { $c.chars == 1 }, Int $n ) { return $c if $c !~~ /<alpha>/; my $out = $c.ord + $n; $out -= 26 if $out > ($c eq $c.uc ?? 'Z'.ord !! 'z'.ord); return $out.chr; } sub rotate(Str $s where {$s.chars}, Int $n = 3) { return ($s.comb.map: { rotate_one( $_, $n % 26 ) }).join( '' ); } die ":\n$*PROGRAM_NAME  _" unless @*ARGS == 2; my Str $mess = @*ARGS[0]; my Int $rotate = @*ARGS[1].Int; say qq|"$mess"  $rotate    "{rotate($mess,$rotate)}".|; 


Beautiful arguments and parameters


In Perl 5, working with parameters is built in @_:

  sub sum { [+] @_ } say sum 100, 20, 3; # 123 


[+] - instruction from Perl 6, but if we write

 my $i = 0; $i += $_ for @_; $i 


then it will work in Perl 5. In Perl 6, just like in Perl 5, the parameters passed to the procedure are accessible through the @_ array. The system is very flexible and does not impose restrictions on the parameters. But this is a rather dreary process, especially if you need to check:
 sub grade_essay { my ($essay, $grade) = @_; die '     Essay' unless $essay ~~ Essay; die '      0  5' unless $grade ~~ Int && $grade ~~ 0..5; %grades{$essay} = $grade; } 


In Perl 5, you had to write isa instead of ~~, and $ grades instead of% grades, but that's all. Now take a look and be terrified how many manual checks would have to be carried out. Do you feel? That's it.

In Perl 5, various convenient modules with CPAN do this, for example, Sub :: Signatures or MooseX :: Declare.

Other methods are available in Perl 6. For example, this example can be written as:
 sub grade_essay(Essay $essay, Int $grade where 0..5) { %grades{$essay} = $grade; } 


Another thing, and without any third-party modules. Sometimes it is convenient to set default values:

  sub entreat($message = ' !', $times = 1) { say $message for ^$times; } 


These values ​​do not have to be constants, but they can also include the previous parameters:

  sub xml_tag ($tag, $endtag = matching_tag($tag) ) {...} 


If the default value is not set, mark the parameter as an optional question mark:

  sub deactivate(PowerPlant $plant, Str $comment?) { $plant.initiate_shutdown_sequence(); say $comment if $comment; } 


What is especially cool is that the parameters can be referenced by name, and transmitted in any order. I could never remember the sequence of parameters:

  sub draw_line($x1, $y1, $x2, $y2) { ... } draw_line($x1, $y1, $x2, $y2); # ,     . draw_line($x1, $x2, $y1, $y2); # ! :-/ 


And so you can refer to them by name:

  draw_line(:x1($x1), :y1($y1), :x2($x2), :y2($y2)); #  draw_line(:x1($x1), :x2($x2), :y1($y1), :y2($y2)); #   ! 


The colon means "now there will be a named parameter", and all together there will be: parameter_name ($ transferred_variable). When the names of parameters and variables are the same, you can use a brief entry:

  draw_line(:$x1, :$y1, :$x2, :$y2); # ! draw_line(:$x1, :$x2, :$y1, :$y2); #   ! 


If the author of any API wants everyone to use the named parameters, he will need to specify colons in the function declaration:

  sub draw_line(:$x1, :$y1, :$x2, :$y2 ) { ... } #    


Named parameters are optional by default. In other words, the top example is equivalent to the following:

  sub draw_line(:$x1?, :$y1?, :$x2?, :$y2?) { ... } #    


If you need to make the parameters mandatory, use the exclamation mark:

  sub draw_line(:$x1!, :$y1!, :$x2!, :$y2!) { ... } #    


Now they need to pass.

What about a variable number of parameters? Easy: make an array parameter preceded by an asterisk:

  sub sum(*@terms) { [+] @terms } say sum 100, 20, 3; # 123 


It turns out that if you do not specify a constraint on the parameters of a future function, then it receives the default constraints * @_. What it means is no constraint, or emulation of Perl 5 behavior.

But the array with the star receives only the parameters in a certain order. If you need to pass named parameters, use the hash:

  sub detect_nonfoos(:$foo!, *%nonfoos) { say " 'foo'   ", %nonfoos.keys.fmt("'%s; } detect_nonfoos(:foo(1), :bar(2), :baz(3)); #  'foo'   'bar', 'baz' 


It is worth noting that you can pass named parameters in the manner of a hash:

  detect_nonfoos(foo => 1, bar => 2, baz => 3); #  'foo'   'bar', 'baz' 


Another difference from Perl 5: by default, parameters are read-only:

  sub increase_by_one($n) { ++$n } my $value = 5; increase_by_one($value); #  


One of the reasons is efficiency. Optimizers like read-only variables. The second is the cultivation of correct habits in the programmer. Functional programming is good for the optimizer and for the soul.

To make the top example work, you need to write it like this:

  sub increase_by_one($n is rw) { ++$n } my $value = 5; say increase_by_one($value); # 6 


Sometimes this is suitable, but sometimes it is easier to change the copy of the parameter:

  sub format_name($first, $middle is copy, $last) { $middle .= substr(0, 1); "$first $middle. $last" } 


The original variable will remain unchanged.

In Perl 6, passing an array or hash does not align the default arguments. Instead, to force alignment, use “|”:

  sub list_names($x, $y, $z) { "$x, $y and $z" } my @ducklings = <huey dewey louie>; try { list_names(@ducklings); } say $!; # '  ; #  1,  3' say list_names(|@ducklings); # 'huey, dewey and louie' 


When the hash is aligned, its contents will be transferred in the form of named parameters.

In addition to arrays and hashes, it is possible to transfer blocks of code:

  sub traverse_inorder(TreeNode $n, &action) { traverse_inorder($n.left, &action) if $n.left; action($n); traverse_inorder($n.right, &action) if $n.right; } 


Three characters act as type limiters:

@ Array (positional)
% Hash (associative)
& Code (called)

$ is a parameter without restrictions.

Do not fall into the trap, trying to assign a type twice - through the name of the type and through the symbol:

  sub f(Array @a) { ... } # ,        sub f( @a) { ... } # ,      sub f(Int @a) { ... } #    


If you've read this far, you deserve another one-liner:

  $ perl6 -e '.fmt("%b").trans("01" => " #").say for <734043054508967647390469416144647854399310>.comb(/.**7/)' ### ## ### # # ## # ## # # ### # # ## # #### # #### # # # # # # # # # # # ## # ## ### 


Regular history


A long time ago, in the not-so-distant kingdom, a student of a Perl 6 programmer named Tim worked on the simple problem of parsing. His boss, Mr. C, asked him to parse logs containing inventory information to make sure that they contain only valid lines. Valid lines should look like this:

  < > <> < > <> 


The student, a little familiar with the regulars, wrote a beautiful regular book to determine the allowable stitches. The code looked like this:

  next unless $line ~~ / ^^ \d+ \s+ \d+ \s+ \S+ \s+ \N* $$ / 


The ~~ operator checks the regularity on the right with respect to the scalar on the left. In the regular calendar, ^^ means the beginning of a line, \ d + - at least one digit, \ S + - at least one non-whitespace character, \ N * any number of characters that are not line breaks, \ s + spaces and $$ line ending. In Perl 6, these characters can be separated by spaces to improve readability. And everything was great.
But then Mr. C decided that it would be nice to extract information from the logs, and not just check it. Tim thought there was no problem and you just need to add some exciting brackets. So he did:

  next unless $line ~~ / ^^ (\d+) \s+ (\d+) \s+ (\S+) \s+ (\N*) $$ / 


After a match, the contents of each pair of brackets is available through the entry $ / [0], $ [1], etc. Or through the variables $ 0, $ 1, $ 2, etc. Tim was happy, Mr. C too.

But then it turned out that on some lines the color was not separated from the description. Such lines looked as follows:

  <part number> <quantity> <description> (<color>) 


At the same time in the description could be any number of characters with spaces. "Hedgehog-korzhiki," thought Tim, "the task has just become very complicated!" But Tim knew where to ask for advice. He quickly went to irc.freenode.org on channel # perl6 and asked there. Someone advised to name parts of the regular season to facilitate working with them and then use alternation to catch all possible alternatives.

First, Tim tried to call parts of the regular season. Looking at the description of regulars in Perl 6, Tim found that he could make this entry:

  next unless $line ~~ / ^^ $<product>=(\d+) \s+ $<quantity>=(\d+) \s+ $<color>=(\S+) \s+ $<description>=(\N*) $$ / 


And then, after finding it, the pieces of the regular list are available through the match object or the variables $, $, $ and $. It was easy, and Tim cheered up. Then he added an alternation so that both variants of the lines could pass the test:

  next unless $line ~~ / ^^ $<product>=(\d+) \s+ $<quantity>=(\d+) \s+ [ | $<description>=(\N*) \s+ '(' $<color>=(\S+) ')' | $<color>=(\S+) \s+ $<description>=(\N*) ] $$ / 


To isolate the alternation from the rest of the regular season, Tim surrounded it with grouping square brackets. These brackets separate the part of the regular schedule, much like round ones, but do not return the separated parts into $ 0 variables, etc. Since he needed to catch the parentheses in the file, Tim used another handy feature of Perl 6: what is enclosed in quotes is searched in the text as it is.

Tim was inspired. He showed the code to Mr. C, and he, too, was inspired! “Well done, Tim!” Said Mr. S. Everyone was happy, and Tim glowed with pride.

However, he then critically looked at his work. Some lines had the color set to “(color)” or “(color)” or “(color)”. As a result, the regulars ranked such colors as the description, and the $ variable did not set at all. It was unacceptable. Tim rewrote the regular season by adding \ s * to it:

  next unless $line ~~ / ^^ $<product>=(\d+) \s+ $<quantity>=(\d+) \s+ [ | $<description>=(\N*) \s+ '(' \s* $<color>=(\S+) \s* ')' | $<color>=(\S+) \s+ $<description>=(\N*) ] $$ / 


It worked, but the regular season began to look awkward. And Tim turned back to channel # perl6.

This time, a user named PerlJam said: “Why don't you put your regular season in a grammar? After all, you practically do this by assigning each piece its own variable. ” “Shchito?” Tim thought. He had no idea what PerlJam was talking about. After a short conversation, Tim seemed to understand what he meant, thanked the user and sat down to write the code. This time the regulars disappeared and turned into a grammar. Here is how she looked:

 grammar Inventory { regex product { \d+ } regex quantity { \d+ } regex color { \S+ } regex description { \N* } regex TOP { ^^ <product> \s+ <quantity> \s+ [ | <description> \s+ '(' \s* <color> \s* ')' | <color> \s+ <description> ] $$ } } # ...  ,    ,   : next unless Inventory.parse($line); 


“Well,” Tim thought, “this time everything is organized well.”

Each of the past variables has evolved into its regular grammar. In the Perl 6 regular, the named regulars are added to the check by enclosing them in angle brackets. The TOP special regular is used when calling Grammar.parse with a scalar. And the behavior is the same - the found part of the expression is stored in the named variable.

And although there is no limit to perfection, Tim and Mr. S were very pleased with the result.

The end!

Classes, attributes, methods, and other


How to write a class in a new Perl 6 object model:

 class Dog { has $.name; method bark($times) { say "w00f! " x $times; } } 


We start with the class keyword. For those who know Perl, 5 class is somewhat similar to package, but out of the box gives a bunch of semantic features.

We then used the has keyword, declaring an attribute that has an accessor method. The point between the $ and the name is tvigil, which reports on the peculiarities of access to the variable. Twidgill-point means "attribute + accessor". More options:

 has $!name; # ,     has $.name is rw; #     


Then the method is declared through the keyword method. Method - as a procedure, only with its entry in the class table of methods. It is available for calling via $ self.

All classes inherit a default constructor named new, which assigns named parameters to attributes. To get an instance of a class, you can write this:

 my $fido = Dog.new(name => 'Fido'); say $fido.name; # Fido $fido.bark(3); # w00f! w00f! w00f! 


Calling methods is done with a dot instead of an arrow in Perl 5. It is 50% shorter and familiar to programmers of other languages.

Of course, there is inheritance. This is how we can create a puppy class:

 class Puppy is Dog { method bark($times) { say "yap! " x $times; } } 


There is a delegation:

 class DogWalker { has $.name; has Dog $.dog handles (dog_name => 'name'); } my $bob = DogWalker.new(name => 'Bob', dog => $fido); say $bob.name; # Bob say $bob.dog_name; # Fido 


Here we declare that calls to the dog_name method of the DogWalker class are redirected to the name method of the Dog class.

Under the layers of all this beauty there is a meta model. Classes, attributes, and methods are represented via meta objects. This is how you can work with objects at runtime:

 for Dog.^methods(:local) -> $meth { say "Dog has a method " ~ $meth.name; } 


The. ^ Operator is an option., But it calls a metaclass - an object representing the class. Here we ask him to give a list of methods defined in the class (: local excludes methods inherited from other classes). And we get not just a list of names, but a list of Method objects. We could call the method itself in this way, but in this case we just print its name.

Meta-programmers who want to extend Perl 6 syntax will be thrilled to learn that using the method keyword actually results in a call to add_method from the meta class. Therefore, in Perl 6 there is not only a powerful syntax for describing objects, but also the ability to extend it for those cases that we have not yet envisaged.

Modules and exports


To create a library in Perl 6, you need to use the module keyword:

 module Fancy::Utilities { sub lolgreet($who) { say "O HAI " ~ uc $who; } } 


Put it in the Fancy / Utilities.pm file somewhere in $ PERL6LIB, and then you can use it like this:

 use Fancy::Utilities; Fancy::Utilities::lolgreet('Tene'); 


Not particularly comfortable. As in Perl 5, it is possible to designate that some things should be available within the scope of the code that loads this module. There is a syntax for this:

 # Utilities.pm module Fancy::Utilities { sub lolgreet($who) is export { say "O HAI " ~ uc $who; } } # foo.pl use Fancy::Utilities; lolgreet('Jnthn'); 


Characters marked “is export” are exported by default. You can also note that characters are exported within a named group:

 module Fancy::Utilities { sub lolgreet($who) is export(:lolcat, :greet) { say "O HAI " ~ uc $who; } sub nicegreet($who) is export(:greet, :DEFAULT) { say "Good morning, $who!"; # Always morning? } sub shortgreet is export(:greet) { say "Hi!"; } sub lolrequest($item) is export(:lolcat) { say "I CAN HAZ A {uc $item}?"; } } 


You can use these tags in the upload code to choose what to import:

 use Fancy::Utilities; #   DEFAULTs use Fancy::Utilities :greet, :lolcat; use Fancy::Utilities :ALL; #  ,    


Multi-procedures are exported by default, they can only be given labels at will:

 multi sub greet(Str $who) { say "Good morning, $who!" } multi sub greet() { say "Hi!" } multi sub greet(Lolcat $who) { say "O HAI " ~ $who.name } 


Classes are a specialization of modules, so you can export something from them too. In addition, you can export a method to use it as a multi-procedure. For example, the following code exports the close method from the IO class so that it can be called as “close ($ fh);”

 class IO { ... method close() is export { ... } ... } 


Perl 6 also supports importing characters from libraries by name.

Junctions


Among the new features of Perl 6, I like combining the most. I imagine not all options for their use, but I know a few handy tricks.

Unions are variables that can contain several values ​​at once. It sounds weird, but let's take an example. Suppose you need to check the variable for compliance with one of the options values:

 if $var == 3 || $var == 5 || $var == 7 { ... } 


Never loved that bullshit. Too many repetitions. Using the any association, this can be written as:

 if $var == any(3, 5, 7) { ... } 


At the core of the language is the concept of “automatic separation of associations into threads” (junctive auto-reading). This means that you can almost always pass a join to where only one value is expected. The code will be executed for all members of the union, and the result will be a combination of all the results obtained.

In the last example, == runs for each element of the union and compares it with $ var. The result of each comparison is written to the new any union, which is then evaluated in a boolean context in an if statement. In a boolean context, an any union is true if any of its members is true, so if $ var matches any of the values ​​of the union, the test will be passed.

This can save code and looks pretty nice. There is another way to write the any union, which can be constructed through the operator |:

 if $var == 3|5|7 { ... } 


If you need to invert the test results, a join variant called none is used:

 if $var == none(3, 5, 7) { ... } 


As you might guess, none in a boolean context is true only if none of its elements is true.

Automatic threading works in other cases:

 my $j = any(1, 2, 3); my $k = $j + 2; 


What will happen? By analogy with the first example, $ k will have the value any (3, 4, 5).

Associations also work with smart search. There are special types of associations that are well suited for this.

Suppose you have a text string and you need to find out if it matches all the regulars from the set:

 $string ~~ /<first>/ & /<second>/ & /<third>/ 


Of course, the regulars first, second and third must be defined. Like the |, & operator that creates the joins, but in this case all the joins will be true if all their members are also true.

The beauty of associations is that they can be passed to almost any function of any library, and this function does not necessarily know that these are associations (but there is an opportunity to recognize them and work with them somehow in a special way). If you have a function that makes a smart comparison of something to a value, you can pass it as a union.

There are still useful things that can be turned with the help of associations. Is the value in the list:

 any(@list) == $value 


Lists work with associations easily and naturally. For example:

 all(@list) > 0; #      ? all(@a) == any(@b); #     @a   @b? 


Rational Fractions


Perl 6 supports rational fractions, which are created in a simple way - dividing one whole by another. To see something unusual is difficult here:

 > say (3/7).WHAT Rat() > say 3/7 0.428571428571429 


Rat is converted to a string by representing a number as a record with a decimal point. But Rat uses an exact internal representation, and not an approximate floating point content like Num:

 > say (3/7).Num + (2/7).Num + (2/7).Num - 1; -1.11022302462516e-16 > say 3/7 + 2/7 + 2/7 - 1 0 


The easiest way to find out what is happening inside the Rat number is using the built-in .perl method. It returns a human-readable string, which through eval turns into the original object:

 > say (3/7).perl 3/7 


You can select Rat components:

 > say (3/7).numerator 3 > say (3/7).denominator 7 > say (3/7).nude.perl [3, 7] 


All standard numeric operations work with Rat. Arithmetic operations with Rat at the output, if possible, also give Rat, and if it is impossible, Num:

 > my $a = 1/60000 + 1/60000; say $a.WHAT; say $a; say $a.perl Rat() 3.33333333333333e-05 1/30000 > my $a = 1/60000 + 1/60001; say $a.WHAT; say $a; say $a.perl Num() 3.33330555601851e-05 3.33330555601851e-05 > my $a = cos(1/60000); say $a.WHAT; say $a; say $a.perl Num() 0.999999999861111 0.999999999861111 


Num has a trendy method that gives out Rat of the given approximation (by default, 1e-6):

 > say 3.14.Rat.perl 157/50 > say pi.Rat.perl 355/113 > say pi.Rat(1e-10).perl 312689/99532 


According to the specification, the numbers written in the source code in the form of decimal, are represented in the form of Rat.

 > say 1.75.WHAT Rat() > say 1.75.perl 7/4 > say 1.752.perl 219/125 


.pick


Another Perl 6 innovation is the .pick method, which allows you to select a random list item. In Perl 5, you could do it like this:

 my @dice = (1, 2, 3, 4, 5, 6); my $index = int (rand() * scalar @dice); print $dice[$index] . "\n"; > 5 


In Perl 6 it will be easier, moreover, you can immediately select several elements:

 my @dice = 1..6; say @dice.pick(2).join(" "); > 3 4 


Well, let's see what kind of attack I get if I throw out 10 d6s ...
 my @dice = 1..6; say @dice.pick(10).join(" "); > 5 3 1 4 2 6 


It turns out that .pick corresponds to its name - if something is removed from the list, then it no longer appears in the list. If you need to allow this item to be selected again, use the word: replace

 my @dice = 1..6; say @dice.pick(10, :replace).join(" "); > 4 1 5 6 4 3 3 5 1 1 


The list does not have to contain elements in any particular order. Here are the bills from "Monopoly":

 my @dice = <1 5 10 20 50 100 500>; say @dice.pick(10, :replace).join(" "); > 20 50 100 500 500 10 20 5 50 20 


But an option for a deck of cards:

 use v6; class Card { has $.rank; has $.suit; multi method Str() { return $.rank ~ $.suit; } } my @deck; for <A 2 3 4 5 6 7 8 9 TJQ K> -> $rank { for < > -> $suit { @deck.push(Card.new(:$rank, :$suit)); } } # Shuffle the cards. @deck .= pick(*); say @deck.Str; 


What does pick (*) do? Consider a little later. For now, think about how to improve the code for the deck of cards and make the class deck.

Good old switch


Although the construction is called the “switch operator”, the keyword is changed to given.

 given $weather { when 'sunny' { say '! ' } when 'cloudy' { say ' . ' } when 'rainy' { say '   ? ' } when 'snowy' { say '! ' } default { say ' ,  .' } } 


It is only necessary to note that not all blocks when are automatically processed — if suddenly several conditions are fulfilled simultaneously, only the first suitable block will be executed.

 given $probability { when 1.00 { say '' } when * > 0.75 { say ' ' } when * > 0.50 { say '' } when * > 0.25 { say '' } when * > 0.00 { say '  ' } when 0.00 { say '  ' } } 


If your $ probability will be equal to 0.80, the code will give 'Most likely', and the rest will not give you. If you need several blocks to work, end them with the word continue (and the block-controlling keywords break / continue are renamed to succeed / proceed).

Note that in the code, the when expression uses both strings and numbers. How does Perl 6 know how to match a given value with a when value, if these things can be completely different types?

In this case, the two values ​​are processed through the so-called. the clever comparison that was mentioned earlier. A clever comparison, written as $ a ~ ~ $ b, is a trickier version of regulars. If a period is specified, a smart comparison checks to see if the value falls into it. If $ b is a class, or a role, or a subtype, smart comparison will check the types. And so on.For values ​​of type Num and Str, their equivalence is checked.

The asterisk passes a clever comparison with anything. And default means the same as “when *”.

But here is something unexpected: given and when can be used independently. While you are saying your “Shield?”, I will explain to you how this:

given is a one-time cycle.

 given $punch-card { .bend; .fold; .mutilate; } 


given here simply sets a topic that is known to perlovikam as $ _. And calls to .method methods are equivalent to calling $ _. Method

when can be used inside any block that specified $ _, explicitly or implicitly:

 my $scanning; for $*IN.lines { when /start/ { $scanning = True } when /stop/ { $scanning = False } if $scanning { #  -,    ,   #   ,  'start'  'stop' } } 


when demonstrates the same behavior as in the given block, i.e. skips remaining code in the block after execution. In the example above, this means moving to the next line.

Another example, with an explicit $ _:

 sub fib(Int $_) { when * < 2 { 1 } default { fib($_ - 1) + fib($_ - 2) } } 


Independence is given and when can be used in other situations. When processing a CATCH block, when given works with a $! Variable containing the last exception caught.

Variants with a modified sequence of writing, when the expression ends with the operator:

  say .[0] + .[1] + .[2] given @list; say '  ,    !' when /^ <[]>+ $/; 


when can be embedded in a given:

  say '!' when // given $castle; 


Since given and when make the code very understandable, here is another perl obfuscation

 $ perl6 -e 'for ^20 {my ($a,$b)=<AT CG>.pick.comb.pick(*);\ my ($c,$d)=sort map {6+4*sin($_/2)},$_,$_+4;\ printf "%{$c}s%{$d-$c}s\n",$a,$b}' GC TA CG GC CG GC TA CG CG CG TA TA TA CG TA TA TA AT CG GC 


Making snowmen


Let me explain to you how to work with complex numbers in Perl 6 using the example of the Mandelbrot set. Then you and higher mathematics, and beautiful pictures, and all sorts of advanced features of the language.

Here is the first version of the script:

 use v6; my $height = @*ARGS[0] // 31; my $width = $height; my $max_iterations = 50; my $upper-right = -2 + (5/4)i; my $lower-left = 1/2 - (5/4)i; sub mandel(Complex $c) { my $z = 0i; for ^$max_iterations { $z = $z * $z + $c; return 1 if ($z.abs > 2); } return 0; } sub subdivide($low, $high, $count) { (^$count).map({ $low + ($_ / ($count - 1)) * ($high - $low) }); } say "P1"; say "$width $height"; for subdivide($upper-right.re, $lower-left.re, $height) -> $re { my @line = subdivide($re + ($upper-right.im)i, $re + 0i, ($width + 1) / 2).map({ mandel($_) }); my $middle = @line.pop; (@line, $middle, @line.reverse).join(' ').say; } 


Lines 3-5 set the size of the chart for the chart. @ * ARGS - the name of the array with the command line parameters. The // operator is a new “defined”, it returns the first argument if it is defined, and in another case the second one. In other words, line 3 sets the height of the value of the first command line argument, and if not, then at 31. The width is set equal to the height. $ max_iterations set the number of repetitions of the main loop, after which it is decided that the point belongs to the set (we work with symmetric figures, so the width must be an odd number)

Lines 7-8 define the boundaries of the picture on the complex plane. It is very easy to add an imaginary component to a number; you just need to add to the number or expression i. It turns out a number of type Complex. It works quite intuitively, for example, when adding Complex to Rat or Int, we again get Complex.

Lines 10 through 17 define the basic function of Mandelbrot. In short, the complex number c is included in the set if the equation z = z * z + c (the initial z is 0) remains bounded if we continue to iterate. So the function is written - we define the cycle running $ max_iterations times. It is known that when the module z grows more than 2, it will not remain limited, so we use the test $ z.abs> 2. If it does, we exit the cycle and return 1, indicating that the point must be black. If the loop passes the maximum number of times and the value does not exceed the limits, we return 0, and the dot turns white.

Lines 19-21 are an auxiliary function that returns an arithmetic progression from $ low to $ high with the number of elements $ count. The type $ low and $ high is not set, so any type that allows basic arithmetic operations will work here. In this script, this is first Num, then Complex.

Lines 23-24 specify the PBM file header.

Lines 26-30 draw a picture. $ upper-right.re is the real part of the complex number $ upper-right, and $ upper-right.im is imaginary. The cycle goes through the real part of the gap. In the cycle, we again take a subset of the imaginary part to compile a list of complex values ​​that we need to test half of this series of pictures. This list is then run through the mandel function using map, and the output is a list of zeros and ones for half the series, including the midpoint.

We do this because the Mandelbrot set is symmetrical about the axis. Therefore, we select the last, midpoint, and expand the list so that it becomes the same list, only backwards (and with the exception of the midpoint). The loan is passed to join to fill the string to the end and output it.

Such an operation produces a set rotated 90 degrees from the usual position, so we get a beautiful snowman like this:

image

You can make this algorithm automatically parallelized through hyperoperators, but there is one catch: the hyperoperator cannot call the normal procedure. They only call class methods and operators. Therefore, we will tweak the Complex class so that it contains the .mandel method

 augment class Complex { method mandel() { my $z = 0i; for ^$max_iterations { $z = $z * $z + self; return 1 if ($z.abs > 2); } return 0; } } for subdivide($upper-right.re, $lower-left.re, $height) -> $re { my @line = subdivide($re + ($upper-right.im)i, $re + 0i, ($width + 1) / 2)>>.mandel; my $middle = @line.pop; (@line, $middle, @line.reverse).join(' ').say; } 


The difference is that mandel is now a method, and the role of the $ c argument is self. And then instead of map ({mandel ($ _)}) we use the hyper-operator.

But if someone does not like to change the Complex class, you can simply turn mandel into an operator.

 sub postfix:<>(Complex $c) { my $z = 0i; for ^$max_iterations { $z = $z * $z + $c; return 1 if ($z.abs > 2); } return 0; } for subdivide($upper-right.re, $lower-left.re, $height) -> $re { my @line = subdivide($re + ($upper-right.im)i, $re + 0i, ($width + 1) / 2)>>; my $middle = @line.pop; (@line, $middle, @line.reverse).join(' ').say; } 


Perl 6 supports Unicode, so you can have fun and set the operator through the snowman symbol.

For the latest version of Rakudo, I remade the script so that it would produce a multi-colored picture. It is slow and eats a lot of memory, but it works stably. Here is a set of Mandelbrot in the resolution of 1001 Ă— 1001, which was calculated 14 hours, and which took 6.4 GB of memory.

image

Roles


Traditionally, OOP classes handle instance management and reuse. Unfortunately, this leads to opposite results: reuse tends to make classes small and minimal, but if they represent a complex entity, they must support everything that is needed for this. In Perl 6, classes retain control of instances, and Roles reuse them.

What is the role? Imagine that we are building a bunch of classes, each of which represents a different type of product. Some will have common functionality and attributes. For example, we may have the role of BatteryPower.

 role BatteryPower { has $.battery-type; has $.batteries-included; method find-power-accessories() { return ProductSearch::find($.battery-type); } } 


At first glance it looks like a class - attributes and methods. However, we cannot use the role on our own. Instead, we insert (compose) it into the class via the does keyword.

 class ElectricCar does BatteryPower { has $.manufacturer; has $.model; } 


The composition takes the attributes and methods from the role and copies them to the class. From this point on, everything works as if the attributes and methods were defined in the class itself. Unlike inheritance, where parent classes are searched for at the moment of distribution of methods, classes have no connection with roles at the time of program execution, except that the class says “yes” in response to the question of whether it performs any specific role.

Interesting things begin when we insert several roles into the class. Suppose we have another role, SocketPower.

 role SocketPower { has $.adapter-type; has $.min-voltage; has $.max-voltage; method find-power-accessories() { return ProductSearch::find($.adapter-type); } } 


The laptop can work from a power outlet or battery, so we insert both roles.

 class Laptop does BatteryPower does SocketPower { } 


We try to compile - and nothing happens. Unlike mixins and inheritance, all roles are in the same position. If both roles offer a method with the same name — in our case, find-power-accessories — then a conflict arises. It can be resolved by giving the class a method that decides what to do.

 class Laptop does BatteryPower does SocketPower { method find-power-accessories() { my $ss = $.adapter-type ~ ' OR ' ~ $.battery-type; return ProductSearch::find($ss); } } 


This is the most typical example of the use of roles, but not the only one. The role can be accepted and inserted into the object (that is, at the class level, and at the object level) through the does and but operators, and this will work like interfaces in Java and C #. But let's not talk about it now - I’d better show you how Perl 6 roles handle generalized programming, or parametric polymorphism.

Roles can take parameters, which can be types or values. For example, you can make a role that we assign to products that need to calculate shipping costs. However, we need to be able to provide other models for calculating shipping costs, so we take a class that can handle shipping costs as a parameter to a role.

 role DeliveryCalculation[::Calculator] { has $.mass; has $.dimensions; method calculate($destination) { my $calc = Calculator.new( :$!mass, :$!dimensions ); return $calc.delivery-to($destination); } } 


Here :: Calculator in square brackets after the name of the role says that we want to capture the object and associate it with the name Calculator inside the role. Then we can use this object to call .new. Suppose we wrote classes that calculate shipping costs for ByDimension and ByMass. Then we can write:

 class Furniture does DeliveryCalculation[ByDimension] { } class HeavyWater does DeliveryCalculation[ByMass] { } 


When defining a role with parameters, you simply specify a set of parameters in square brackets, and when using a role, a list of arguments is placed in square brackets. Therefore, you can use the full power of Perl 6 parameter sets in this case. And besides, the default roles are multi, multiple, so you can set many roles with the same name, which take different types and different types of parameters.

In addition to the ability to parameterize roles through square brackets, it is also possible to use the keyword of if each of the roles accepts only one parameter. Therefore, after the following announcements:

 role Cup[::Contents] { } role Glass[::Contents] { } class EggNog { } class MulledWine { } 


You can write like this:

 my Cup of EggNog $mug = get_eggnog(); my Glass of MulledWine $glass = get_wine(); 


It can even be folded as follows:

 role Tray[::ItemType] { } my Tray of Glass of MulledWine $valuable; 


The last example is simply a more readable version of the Tray [Glass [MulledWine]] entry.

Anything

“Anything” is a type in Perl 6 that represents everything that makes sense in this context.

Examples:

 1..* #   my @x = <abcd e>; say @x[*-2] #     #  'd' say @x.map: * ~ 'A'; #  A  ,   #   say @x.pick(*) #     @x #     


So how does this magic work?

Some examples are simple. * at the position of a member of the expression produces a Whatever object, and some built-in functions (for example, List.pick) know what to do with it. By the way, Perl 6 parses the file predictively, that is, when the compiler reads the code, it always knows whether it has encountered a member of the expression or an operator.

 say 2 + 4 | | | | | | | +   () | | +  ( +) | +   () +   (listop),       


Therefore, in the record

 * * 2 


the first * is treated as a member of the expression, the second - as an operator. This example generates a block of code -> $ x {$ x * 2}.

 my $x = * * 2; say $x(4); # says 8 


Same Makar,

 say @x.map: * ~ 'A'; 


- this is just a short entry for

 say @x.map: -> $x { $x ~ 'A' }; 


but

 say @x.map: *.succ; 


just a short record for

 say @x.map: -> $x { $x.succ }; 


Whatever is useful in sorting - for example, to sort the list in numerical order (the prefix + means to convert to a numeric form):

 @list.sort: +* 


To sort the list according to the rules for strings (the prefix ~ means to convert the value to a string view):

 @list.sort: ~* 


Little tricks


One of the simple and powerful ideas of Perl 6 is introspection. For PL, this is the mechanism by which you can ask questions about a language using the language itself. For example, instances of objects have methods that tell which class it belongs to, methods that give a list of available methods, and so on.

Even the procedure has a method reporting the name of this procedure:

  sub foo (Int $i, @stuff, $blah = 5) { ... } say &foo.name; #  "foo" 


Although it does not look very meaningful, but remember: procedures can be assigned to scalars, you can give them aliases or create them on the fly, so the procedure name is not always obvious when looking at the code:

  my $bar = &foo; # ...    ... say $bar.name; #  - , ? 


Here are some other methods for learning the procedures:

  say &foo.signature.perl; #    ? say &foo.count; #    ? say &foo.arity; #     ? 


The last parameter is arity, or the number of required parameters. Thanks to introspection in Perl 6, you can do things previously impossible. For example, in Perl 5, the map block takes a list of items one at a time and converts to one or more new items from which it creates a new list. Because Perl 6 knows how many arguments are expected, it can take as much as it needs.

  my @foo = map -> $x, $y { ... }, @bar; #      @bar   @foo my @coords = map -> $x, $y, $z { ... }, @numbers; #   @numbers     


Another advantage is a more convenient mechanism for sorting arrays according to criteria different from string comparison. If you specify a sorting procedure for an array, it usually takes two arguments — the compared items from the array. If we wanted to sort people by their karma, we would write something like:

 #!/usr/bin/perl6 use v6; class Person { has $.name; has $.karma; method Str { return "$.name ($.karma)" } #    } my @names = <Jonathan Larry Scott Patrick Carl Moritz Will Stephen>; my @people = map { Person.new(name => $_, karma => (rand * 20).Int) }, @names; .say for @people.sort: { $^a.karma <=> $^b.karma }; 


But.Thanks to introspection there is another option. By passing a procedure that accepts only one parameter, Perl 6 can automatically create the equivalent of the Schwartz transform.
ru.wikipedia.org/wiki/Schwartz Conversion

  .say for @people.sort: { $^a.karma }; 


However, since we have only one parameter, $ _ is implicitly specified in the procedure, so you can get rid of extra characters:

  .say for @people.sort: { .karma }; 


This example calls the .karma method for each element of the array once (rather than twice, as for comparison in the usual case) and then sorts the array by these results.

Another trick is the built-in type system. In the example above, I did not declare the need for numeric sorting, since perl itself would guess that we use numbers. If I had to force the sorting type, I would use + or ~:

  .say for @people.sort: { +.karma }; #  .say for @people.sort: { ~.karma }; #  


In the .min and .max methods this is especially convenient. They also adopt a procedure for determining the sorting criteria:

  say @people.min: { +.karma } #  say @people.max: { ~.name } #  


This can also be written using Whatever:

  .say for @people.sort: *.karma; say @values.min: +*.karma; say @values.max: ~*.name; 


Grammar and Action


Suppose we have a bunch of text to parse. Is Perl meant for this? We specify the problem: the following text describes the questions and answers:

 pickmany:     ? ac:  ac:  ac:  ai:  pickone:     ? ac:  ai:  ai:  ai:  


In Perl 6, for parsing, I define Grammar. This is a special kind of namespace that contains regular expressions. We will also set several named expressions to divide the parsing task into parts.

 grammar Question::Grammar { token TOP { \n* <question>+ } token question { <header> <answer>+ } token header { ^^ $<type>=['pickone'|'pickmany'] ':' \s+ $<text>=[\N*] \n } token answer { ^^ \s+ $<correct>=['ac'|'ai'] ':' \s+ $<text>=[\N*] \n } } 


By default, in grammars, spaces are ignored, and matches are searched for the entire line — as if the modifiers / x and / s are included in Perl 5.

'token' is one of the three identifiers used to define the regular schedule, including 'regex', 'token', and 'rule'.
'regex' is a simple version, while the other two simply add the options
'token' prohibits returns back, and the 'rule' prohibits returns and includes a literal search for spaces specified in the regular schedule. We will not use the 'rule'.

The syntax is used to call another named regular. '^^' is used to indicate the beginning of a line, as opposed to '^', which marks the beginning of the whole text. Square brackets are a grouping that does not affect the array of found parts of a string, an analogue (?:) In Perl 5.

The = sign assigns a name to the right side on the left side. Let's see what happens if we search for this grammar and display the search results:

 my $text = Q { pickmany:     ? ac:  ac:  ac:  ai:  pickone:     ? ac:  ai:  ai:  ai:  }; my $match = Question::Grammar.parse($text); say $match.perl; 


We will not include the issue in 232 lines here as a whole. Consider one part, questions.

 #   for $match<question>.flat -> $q { say $q<header><text>; } 


.flat is used because $ match is an array contained in a scalar container. Angle brackets are equivalent to the following:

 #   for $match{'question'}.flat -> $q { say $q{'header'}{'text'}; } 


This shows that the object contains named items as hash values, and repetitions are contained in an array. If we had an array of found results created by parentheses, as in Perl 5 (), its elements could be accessed via the positional interface using square brackets (as when working with arrays).

The next step is to make several classes and multiply them based on the object. Class definitions:

 class Question::Answer { has $.text is rw; has Bool $.correct is rw; } class Question { has $.text is rw; has $.type is rw; has Question::Answer @.answers is rw; } 


Creating Question objects from the search results is not so difficult, but it looks ugly:

 my @questions = $match<question>.map: { Question.new( text => ~$_<header><text>, type => ~$_<header><type>, answers => $_<answer>.map: { Question::Answer.new( text => ~$_<text>, correct => ~$_<correct> eq 'ac', ) }, ); }; 


Bearing in mind that any repetition in the regular list leads to the appearance of an array in the object, we run the map by attribute, and build for each Question object. Each entry pulls an array of, which we also pass with the help of map, building a list of Question :: Answer objects. We convert the values ​​found from Math objects to strings.

This approach does not scale. It would be more convenient to build objects on the fly. To do this, pass the object as an argument: action to the grammar's .parse () method. The parsing engine will then call methods with the same name that the regular schedule has, to which the Match object will be passed as an argument. If the method calls make () at run time, the argument to make () is written as an attribute .ast (“Abstract Syntax Tree”,abstract syntax tree of the Match object.

– . , , :

 class Question::Actions { method TOP($/) { make $<question>».ast; } method question($/) { make Question.new( text => ~$<header><text>, type => ~$<header><type>, answers => $<answer>».ast, ); } method answer($/) { make Question::Answer.new( correct => ~$<correct> eq 'ac', text => ~$<text>, ); } } 


$/ — Match, , $_ — . ($ $[1]) $/ ($/ $/[1]). , , $1, $2, $3 Perl 5.

TOP , .ast $. 'make' , - .ast Match, , 'make' 'question'.

In the 'question' method, we create a new Question object, passing all attributes from the match object to it, and assigning to its 'answer' attributes a list of objects that is received each time the 'answer' regular question 'question' is called.

In the 'answer' method, we do the same thing, assigning the comparison result to the 'correct' attribute to satisfy the attribute type 'bool'.

When parsing, we make an instance of this new class and pass the object as the: action parameter to the grammar's .parse method, and then we get the constructed object from the .ast attribute from the search object that it returns.

my $ actions = Question :: Actions.new ();
my questions = Question :: Grammar.parse ($ text,: actions ($ actions)). ast.flat;

Now you can check the created object to make sure that everything goes according to plan:

 for @questions -> $q { say $q.text; for $q.answers.kv -> $i, $a { say " $i) {$a.text}"; } } 


And for completeness, let's add a method in Question that will ask a question, get an answer and evaluate it.

Let's start with the presentation of the question, answers and input request:

  method ask { my %hints = ( pickmany => "   ,   ", pickone => "   ", ); say "\n{%hints{$.type}}\n"; say $.text; for @.answers.kv -> $i, $a { say "$i) {$a.text}"; } print "> "; 


Now we will get the string from STDIN and extract the numbers from it:

  my $line = $*IN.get(); my @answers = $line.comb(/<digit>+/)>>.Int.sort 


'comb' is the opposite of 'split', in the sense that we determine what we need to leave, and not what we need to throw away. The advantage is that we don’t need to choose a separating character. The user can enter “1 2 3 ″,“ 1,2,3 ”or even“ 1, 2 and 3 ″. Then, by calling the hyperoperator method, we create an array of integers from the array of found characters and sort it.

Now let's create an array of indices of all the correct answers, and find out the correct answers.

  my @correct = @.answers.kv.map({ $^value.correct ?? $^key !! () }); if @correct ~~ @answers { say ",  !"; return 1; } else { say " ,  "; return 0; } } 


Call it for each of the questions and collect the results through the map of our new method:

 my @results = @questions.map(*.ask); say "\nFinal score: " ~ [+] @results; 


The results will be something like this:

 [sweeks@kupo ~]$ perl6 /tmp/questions.pl    ,        ? 0)  1)  2)  3)  > 0 1 2 ,  !         ? 0)  1)  2)  3)  > 1  ,    : 1 


Here is the full text of the program:

 class Question::Answer { has $.text is rw; has Bool $.correct is rw; } class Question { has $.text is rw; has $.type is rw; has Question::Answer @.answers is rw; method ask { my %hints = ( pickmany => "    ,   ", pickone => "    ", ); say "\n{%hints{$.type}}\n"; say $.text; for @.answers.kv -> $i, $a { say "$i) {$a.text}"; } print "> "; my $line = $*IN.get(); my @answers = $line.comb(/<digit>+/)>>.Int.sort @correct = @.answers.kv.map({ $^value.correct ?? $^key !! () }); if @correct ~~ @answers { say " ,  !"; return 1; } else { say "  ,  "; return 0; } } } grammar Question::Grammar { token TOP { \n* <question>+ } token question { <header> <answer>+ } token header { ^^ $<type>=['pickone'|'pickmany'] ':' \s+ $<text>=[\N*] \n } token answer { ^^ \s+ $<correct>=['ac'|'ai'] ':' \s+ $<text>=[\N*] \n } } class Question::Actions { method TOP($/) { make $<question>».ast; } method question($/) { make Question.new( text => ~$<header><text>, type => ~$<header><type>, answers => $<answer>».ast, ); } method answer($/) { make Question::Answer.new( correct => ~$<correct> eq 'ac', text => ~$<text>, ); } } my $text = Q { pickmany:     ? ac:  ac:  ac:  ai:  pickone:     ? ac:  ai:  ai:  ai:  }; my $actions = Question::Actions.new(); my @questions = Question::Grammar.parse($text, :actions($actions)).ast.flat; my @results = @questions.map(*.ask); say "\n : " ~ [+] @results; 


Operator Overloading


Perl 6 allows you to overload existing operators and define new ones. Operators are simply multi-procedures with a special name, and standard rules for multi-procedures are used to determine the desired variant of the operator.

A common example is the definition of a factorial operator, similar to a mathematical notation:

 multi sub postfix:<!>(Int $n) { [*] 1..$n; } say 3!; 


The first part of the definition is a syntactic category (prefix, postfix, infix, circumfix, or postcircumfix). After the colon are the angle brackets, in which the operator is written. In the case of circumfix, two pairs of brackets are required, but for all the rest one is sufficient, inside which there may be several characters. In the example, we defined a postfix operator!, Working with integers.

You can set additional attributes, such as tighter, equiv, and looser, which set the order of priority compared to other operators.

If you specify a replacement for an existing statement, a new definition is simply added to its multiprocessing set. For example, you can define your class and determine that its objects can be added with the + operator:

 class PieceOfString { has Int $.length; } multi sub infix:<+>(PieceOfString $lhs, PieceOfString $rhs) { PieceOfString.new(:length($lhs.length + $rhs.length)); } 


Of course, real-life examples are more complex and include several variables. You can set a string equality check:

 multi sub infix:<==>(PieceOfString $lhs, PieceOfString $rhs --> Bool) { $lhs.length == $rhs.length; } 


What should be avoided is the overload of the prefix: <~> operator (conversion to string). If you do this, you will not be able to intercept all string conversions. Instead, it's better to define your class's Str method, which will do the job:

 use MONKEY_TYPING; augment class PieceOfString { method Str { '-' x $.length; } } 


And this method will be called by the traditional operator ~. Methods whose names match the types are used to convert types, so you can specify Str and Num methods for your classes in cases where this makes sense.

And since the source code for Perl 6 is written in Unicode, you can define new statements using all the richness of characters.

Lazy fruits of the Garden of Eden

Consider an infrequently found structure in other languages ​​- an iterator constructor called gather.

Historically, many perloviki know convenient functions map, grep and sort:

 my @squares = map { $_ * $_ }, @numbers; my @primes = grep { is-prime($_) }, @numbers; 


map and grep are especially good when you have learned to build them into chains:

 my @children-of-single-moms = map { .children }, grep { !.is-married }, grep { .gender == FEMALE }, @citizens; 


This led to the creation of the Schwartz transformation - an idiom for caching in that case. when sorting is resource intensive:

 my @files-by-modification-date = map { .[0] }, #  sort { $^a[1] <=> $^b[1] }, map { [$_, $_ ~~ :M] }, #    @files; 


, sort:

 my @files-by-modification-date = sort { $_ ~~ :M }, @files; 


gather? map grep.

 sub mymap(&transform, @list) { gather for @list { take transform($_); } }; sub mygrep(&condition, @list) { gather for @list { take $_ if condition($_); } }; 


gather , . take . push :

 my @result = gather { take $_ for 5..7 }; #  - my @result; push @result, $_ for 5..7; #   


, gather – , map, grep sort . , … , , , .

 sub incremental-concat(@list) { my $string-accumulator = ""; gather for @list { take ~($string-accumulator ~= $_); } }; say incremental-concat(<ab c>).perl; # ["a", "ab", "abc"] 


The example is more convenient than the map, because between iterations we need to process $ string-accumulator.

The second property of the gather - although take calls must occur in the scope of the gather block, they need not be in the lexical area - only in the dynamic. For those who are not clear differences, I will explain:

 sub traverse-tree-inorder(Tree $t) { traverse-tree-inorder($t.left) if $t.left; take transform($t); traverse-tree-inorder($t.right) if $t.right; } my $tree = ...; my @all-nodes = gather traverse-tree-inorder($tree); 


Here we wrap the & traverse-tree-inorder call in the gather instruction. The instruction itself does not contain lexically take calls, but the called procedure contains, and thus take inside it remembers that it is in the context of the gather. This is the dynamic context.

The third property of the gather: it is "lazy." In the case of a tree traversal code, this means: when assigning @ all-nodes, the tree has not yet been bypassed. The traversal begins only when accessing the first element of the array, @ all-nodes [0]. And it stops when the leftmost vertex is found. Request @ all-nodes [1] - and the detour will resume from where it ended, to stop after it finds the second vertex.

That is, the code in the gather block starts and stops so as not to do the work more than it is asked. This is the "lazy" behavior.

In fact, this is a deferred execution. Perl 6 promises to execute the code inside the gather block, but only if you need its information. Interestingly, almost all arrays are lazy by default, and reading strings from a file too. map and grep is not just possible to create with the help of the gather, they themselves are also lazy. This behavior opens up the possibilities of programming threads and using infinite arrays.

 my @natural-numbers = 0 .. Inf; my @even-numbers = 0, 2 ... *; #   my @odd-numbers = 1, 3 ... *; my @powers-of-two = 1, 2, 4 ... *; #   my @squares-of-odd-numbers = map { $_ * $_ }, @odd-numbers; sub enumerate-positive-rationals() { #     take 1; for 1..Inf -> $total { for 1..^$total Z reverse(1..^$total) -> $numerator, $denominator { take $numerator / $denominator; } } } sub enumerate-all-rationals() { map { $_, -$_ }, enumerate-positive-rationals(); } sub fibonacci() { gather { take 0; my ($last, $this) = 0, 1; loop { # infinitely! take $this; ($last, $this) = $this, $last + $this; } } } say fibonacci[10]; # 55 #   ,     sub merge(@a, @b) { !@a && !@b ?? () !! !@a ?? @b !! !@b ?? @a !! (@a[0] < @b[0] ?? @a.shift !! @b.shift, merge(@a, @b)) } sub hamming-sequence() # 2**a * 3**b * 5**c, where { all(a,b,c) >= 0 } gather { take 1; take $_ for merge( (map { 2 * $_ } hamming-sequence()), merge( (map { 3 * $_ }, hamming-sequence()), (map { 5 * $_ }, hamming-sequence()) )); } } 


The last procedure is solving the Hamming problem on Perl 6.

And here is another “abracadabra” that implements a cellular automaton that suddenly draws a Christmas tree.

 $ perl6 -e 'my %r=[^8]>>.fmt("%03b") Z (0,1,1,1,1,0,0,0);\ say <. X>[my@i=0 xx 9,1,0 xx 9];\ for ^9 {say <. X>[@i=map {%r{@i[($_-1)%19,$_,($_+1)%19].join}},^19]};' .........X......... ........XXX........ .......XX..X....... ......XX.XXXX...... .....XX..X...X..... ....XX.XXXX.XXX.... ...XX..X....X..X... ..XX.XXXX..XXXXXX.. .XX..X...XXX.....X. XX.XXXX.XX..X...XXX 


Standard grammar perl 6


. , – , ?

Perl 6, . , . , Perl 6 , . , , ..

– . . :

  rule statement { | <if_statement> | <while_statement> | <for_statement> | <expr> } rule if_statement { 'if' <expr> <statement> } rule while_statement { 'while' <expr> <statement> } rule for_statement { 'for' '(' <expr> ';' <expr> ';' <expr> ')' <stmt> } 


:

  proto token statement { <...> } rule statement:sym<if> { 'if' <expr> <statement> } rule statement:sym<while> { 'while' <expr> <statement> } rule statement:sym<for> { 'for' '(' <expr> ';' <expr> ';' <expr> ')' <stmt> } rule statement:sym<expr> { <expr> } 


We determine that it is looking for matches in any of the described constructions, but the PDF version is much easier to expand. In the first version, adding a new statement (for example, “repeat..until”) would require rewriting the entire rule statement. But with PDF, add one more rule:

  rule statement:sym<repeat> { 'repeat' <stmt> 'until' <expr> } 


This rule is added to the PDP candidates. And it works in the new version of the grammar:

  grammar MyNewGrammar is BaseGrammar { rule statement:sym<repeat> { 'repeat' <stmt> 'until' <expr> } } 


MyGrammar parsit is the same as BaseGrammar, with the additional definition of the operator repeat..until.

Another useful component of standard grammar is advanced error diagnostics. Instead of messages like “there is an error,” the language offers options for its solution. In addition, efforts have been made to help those who migrate from Perl 5 to Perl 6 while tracking constructs that have changed their meaning. For example, if you try to use else with the unless block, you will get the error:

  unless does not take "else" in Perl 6; please rewrite using "if" 


Or, using the ternary operator (? :), the parser will return:

  Unsupported use of "?:"; in Perl 6 please use "??!!" 


We hope you enjoyed our notes on the new Perl 6 language as applied to the Rakudo compiler.

Source: https://habr.com/ru/post/248713/


All Articles