Perl 6 and Rakudo: 2009 Notes
- Transfer
A series of articles on Perl 6 and Rakudo, one of the compilers that support the Perl6 specification. This article is compiled from notes from 2009.
At the moment there are several incomplete implementations of Perl 6. The most complete of them is the Rakudo compiler ( download ).
Git users can create their copy using the following commands:
Alternatively, you can compile it from source by downloading them from github.com/rakudo/rakudo/tree/nom
There is a ready-made binary installer for Windows. Installer versions and source code are available here.
By executing the perl6 command, you will end up in a REPL environment where you can play with various language commands.
Lines beginning with “>” are commands, and all the rest are system responses. The first example is a simple say statement. The second creates a rational number and requests its type (Rat). The third one takes a list of numbers from 1 to 999, filters out those that are not divisible by 3 or 5, adds them up and displays the result.
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 have forgotten how to use it, read perldoc. But don't go deep, just browse.
So .fmt. Here are some ways to use it to format strings and integers.
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:
And here is its use with hashes (mappings):
However, in the case of a hash, the order of issue may differ from the above. There is also .fmt for pairs, but it works the same as hashes. .fmt is a convenient tool for changing a value or an array of values and converting 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 New Year present for you as a one-line drawing of a Christmas tree:
Option for Windows (other quotation marks required):
In Perl 5, $ scalar variables could contain either a reference or a value. The value could be anything - an integer, a string, a non-integer, a date. Flexibility at the expense of 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:
Other type examples:
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 the procedure using the same name for different cases. Here is an example:
Result:
The authors of perl-modules are used to supplying with the modules that they release to the world, a set of tests. This tradition is maintained in perl 6 through specific instructions.
The classic way to write tests in perl is to output data using the Test Anything Protocol. But you do not have to do it manually - you can use the module.
Suppose you have a factorial function:
It doesn't matter how it works so far - we just want to find out if it works correctly. Let's check:
Run:
More 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 verify that calling the function with an argument other than an integer results in an error.
Test output indicates that 6 tests are running, and then on each line displays the test results (ok - if passed, not ok - if failed), test number and description.
When starting a large number of tests, I don’t want to see them all in detail, but I want to see the results. The prove command does just that:
It is customary to add test files to a separate t / directory and run prove recursively on all files from the directory with the extension .t:
If you put this line in the Makefile, you can simply type make test to run the tests.
Earlier we saw an interesting implementation of the factorial function:
But how does it work? Perl 6 has several meta-operators that modify existing operators that are becoming more powerful. The square brackets are the reduce meta-operator, which places the operator specified inside the brackets between all elements of the list. For example,
means the same as
Thus, we can easily summarize the elements of a list:
Almost all operators can be placed in square brackets:
Therefore, in the factorial, the expression [*] 1 .. $ n takes the value of the multiplied list elements from 1 to $ n.
Another metaoperator is hyper. By placing "or" (or their ASCII counterparts >> 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 pairwise adding the @a and @b elements:
In Perl 5, we would have to write something like
hyper is used on various operators, including user-defined operators:
Instead of arrays, you can use scalars:
Of course, reduce and hyper can be combined:
There are many more meta-operators, 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:
Go into hyperspace
before we continue the study metaoperatorov, lsay introduce an auxiliary function that prints out nicely formatted lists. Defining it through our you can then use it in a REPL environment:
Let's start with a simple one: add two lists of the same length:
If the lengths of the lists are the same, both recording options are identical. But if their lengths differ:
The rule is this: what the sharp end of the hyperoperator indicates can be extended if it is shorter than what is on its other end. Renewal occurs by repeating the last element of the list. What the "blunt" end points to is not subject to renewal. All combinations are possible, for example, when only the left side (<< + <<), only the right side (>> + >>), both sides (<< + >>), or none of the sides (>> + << ) Single scalars can also be extended:
These are the basics of using hyperoperators. They can also be used with postfix and prefix operators:
It is also possible:
In this case >>. calls a method on each item in the list.
If you want to write array >>. Say, then better is not necessary. The use of hyperoperators implies that the operation can be performed in parallel, and the order of operations on the list is not fixed.
Hyperoperators work not only with built-in operators. You can define your own operator, and they will also work with it. They should work (but do not work yet) with in-place operators - for example, the instruction @a >> / = >> 2 should divide the entire array by 2. They work with multidimensional 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.
Any programmer knows how useful loops are. A common example of using loops is a foreach loop to traverse arrays. This is exactly the kind of keyword we used in Perl 5, although you could use for, which was more reminiscent of the C style.
In Perl 6, everything is different.
Now for is used for passing through lists. foreach is no longer there, and the word loop is used for C-style. For now, we will consider only for, which is a new, flexible and powerful feature of the language:
Immediately noticeable is the absence of parentheses around the list. Typically, in Perl 6 you need fewer brackets than in Perl 5. The default variable, like 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 without arguments - you need to write either .say or $ _. Say
Instead of a simple block, you can use a "pointed" block that allows you to specify the name of a loop variable:
The “pointed” block resembles an anonymous procedure; it just does not catch exceptions. And if you write return inside such a block, then the whole procedure that called it will exit. Such blocks take more than one parameter. And what happens if you write like this:
At startup, you will receive:
That is, you went through the list, sorting 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're going through? Of course, you can use an array variable:
But in simple cases, we can use map instead:
Or a hyperoperator, if the sequence is not important to us:
But we are not talking about that now. You can create a list through the span operator <..>:
Often you need to create a list of $ n numbers starting with 0. You could write 0 .. $ n-1 or use the gap constructor 0 .. ^ $ n, but in Perl 6 there is a shorter way through the ^ prefix:
At the output we get:
The reasons for using C-style loops are to know which of the list items we are on right now, or need to go through several arrays at the same time. Perl 6 also has a short entry for this via the Z (zip) statement:
If both arrays have the same length, $ one will go through all the elements of @ array1, and $ two will go 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:
If endless lists are not to your liking:
which will lead to the same result, but the most elegant option is
array .kv returns keys and values, and for an array, keys are just the indices of the elements.
Thus, you can go through at least four arrays at the same time:
Just as static types limit the value of a variable, so constraints allow you to control the operation of procedures and methods. In many PLs, it is necessary to pass parameters to the procedure and check the received values. With restrictions, verification can be done right at the announcement. Example: we do not need even numbers. In Perl 5, you could write this:
In Perl 6 you can make it simpler:
If you call very_odd with an even parameter, you get an error. For convenience, you can reload the procedure, and work with any numbers:
Parameter restrictions are conveniently used in conjunction with the .comb method. What is .comb? (comb - comb). In the case of hair and a comb, you separate the strands and lay them on your head. .comb is the opposite of .split. If the last method allows you to split the string by those elements that you do not need, then .comb separates it by the necessary elements. Here is a simple example:
The first line produces “P | e | r | l | A | d | v | e | n | t”: it takes each letter and places it in a temporary array, which is then joined via “|”. The second line acts similarly, only there the greatest possible number of letters is captured in a row, and the result is “Perl | Advent”.
But .comb is much more powerful. After you comb the string, you can manipulate the strands. If you have a string of ASCII characters, you can use hyperoperators to replace each piece with the ASCII equivalent:
You can write this through the .map method:
As usual, there is more than one way to do something.
But the task is more complicated: I present to you the ancient Caesar cipher through the restrictions of the parameters, .comb and .map
In Perl 5, working with parameters is built through @_:
[+] - instruction from Perl 6, but if we write
this 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 necessary to check:
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 horrified how many manual checks would have to be done. 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 like this:
Another thing, and without any third-party modules. Sometimes it’s convenient to set default values:
These values do not have to be constants, but they can also include the previous parameters:
If no default value is specified, mark the parameter as an optional question mark:
Which is especially cool, parameters can be referenced by name, and pass them in any order. I could never remember the sequence of parameters:
And so you can refer to them by name:
The colon means "there will now be a named parameter", and all together will be: parameter_name ($ passed_variable). When the names of the parameters and variables coincide, you can use a short notation:
If the author of an API wants everyone to use named parameters, he will need to specify colons in the function declaration:
Named parameters are optional by default. In other words, the top example is equivalent to the following:
If you need to make the parameters mandatory, use an exclamation mark:
Now they must be transferred.
What about a variable number of parameters? Easy: make the parameter an array preceded by an asterisk:
It turns out that if you do not set restrictions on the parameters of the future function, then it receives the default restrictions * @_. What this means is the absence of restrictions, or emulation of the behavior of Perl 5.
But an array with a star receives only parameters in a certain order. If you need to pass named parameters, use a hash:
It is worth noting that you can pass named parameters in the manner of a hash:
Another difference from Perl 5: by default, options are read-only:
One reason is efficiency. Optimizers like read-only variables. The second is to bring up the right habits in a programmer. Functional programming is good for both the optimizer and the soul.
For the top example to work, you need to write it like this:
Sometimes this works, but sometimes it's easier to change a copy of the parameter:
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 “|":
When a 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 code blocks:
Three characters act as type limiters:
@ Array (positional)
% Hash (associative)
& Code (called)
$ - parameter without restrictions.
Do not fall into the trap of trying to assign a type twice - through the type name and through the symbol:
If you read up to this point, you deserve one more one-liner:
Once upon a time in a not-so-distant kingdom, a Perl 6 student named Tim worked on a simple parsing problem. His boss, Mr. C, parsed him for logs containing inventory information to make sure they only contain valid lines. Valid strings should look like this:
A student who is a little familiar with regulars wrote a beautiful regular for determining the permissible lines. The code looked like this:
The ~~ operator checks the regularity on the right with respect to the scalar on the left. In the regular season, ^^ 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 $$ end of line. In Perl 6, these characters can be separated by spaces to improve readability. And everything was wonderful.
But then Mr. C decided that it would be nice to extract information from the logs, and not just check it. Tim thought there were no problems and just add exciting brackets. So he did:
After the match, the contents of each pair of brackets is accessible 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:
Moreover, the description could have any number of characters with spaces. Hedgehogs, thought Tim, the task has just become very complicated! But Tim knew where to ask for advice. He quickly went to irc.freenode.org in channel # perl6 and asked there. Someone advised to name the parts of the regular season to make it easier to work with them and then use alternation to catch all possible alternatives.
At first Tim tried to name the parts of the regular season. After looking at the description of regulars in Perl 6, Tim discovered that he could make such an entry:
And then, after finding, the pieces of the regular season are available through the match object or the variables $, $, $ and $. It was simple, and Tim perked up. He then added alternation so that both line options could pass the test:
To isolate the alternation from the rest of the regular season, Tim surrounded her with grouping square brackets. These brackets separate part of the regular, like round ones, but do not return the separated parts to $ 0 variables, etc. Since he needed to catch parentheses in the file, Tim took advantage of another convenient feature of Perl 6: what is enclosed in quotation marks is looked up in the text as it is.
Tim was encouraged. He showed the code to Mr. C, and he was also inspired! “Well done, Tim!” Said Mr. C. Everyone was happy, and Tim shone with pride.
However, then he critically looked at his work. For some lines, the color is set to “(color)” or “(color)” or “(color)”. As a result, the regularity assigned such colors to the description, but did not set the $ variable at all. That was unacceptable. Tim rewrote the regular season, adding \ s * there too:
It worked, but the regular 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 in grammar? After all, you are practically doing this, assigning each variable its own variable. ” Shield? Tim thought. He had no idea what PerlJam was talking about. After a short conversation, Tim seemed to understand what was meant, thanked the user, and sat down to write code. This time, the regularity disappeared and turned into a grammar. Here's what she looked like:
Well, Tim thought, this time everything is organized quite well.
Each of the past variables has become its own regularity within the grammar. In the Perl 6 Regular, named regulars are added to the test by enclosing them in angle brackets. The special TOP 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 a named variable.
And although there is no limit to perfection, Tim and Mr. C were very pleased with the result.
The end!
How to write a class in the new Perl 6 object model:
We start with the class keyword. For those who know Perl 5, the class is somewhat similar to package, but “out of the box” provides a bunch of semantic possibilities.
Then we used the has keyword, declaring an attribute that has an accessor method. The point between $ and name is a tweedle that reports on the features of accessing the variable. Twigil-dot means “attribute + accessor”. More options:
The method is then declared via the method keyword. A method is like a procedure, only with its own entry in the class method table. It is available for calling through $ 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 like this:
Method calls are made with a dot instead of an arrow in Perl 5. It is 50% shorter and familiar to programmers in other languages.
Of course, there is inheritance. This is how we can create a puppy class:
There is also a delegation:
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 through meta objects. This is how you can work with objects at runtime:
The. ^ Operator is an option., But it calls a metaclass - an object that represents 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 simply display its name.
Meta-programming enthusiasts who want to extend Perl 6 syntax will be thrilled to learn that using the method keyword actually causes add_method to be called 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 envisioned.
To create a library in Perl 6, you need to use the module keyword:
Put this in the Fancy / Utilities.pm file somewhere in $ PERL6LIB, and then you can use it like this:
Not particularly convenient. As in Perl 5, it is possible to indicate that some things should be available in the scope of the code that loads this module. There is a syntax for this:
Marked “is export” characters are exported by default. You can also note that characters are exported as part of a named group:
These tags can be used in the loading code to choose what to import:
Multi-procedures are exported by default, they can only be given labels as desired:
Classes are a specialization of modules, so you can also export something from them. 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);”
Perl 6 also supports importing characters from a library by name.
Among the new features of Perl 6, I like associations most of all. I do not represent all the options for their use, but I know a few convenient tricks.
Unions are variables that can contain multiple values at once. It sounds strange, but let's look at an example. Suppose you need to check a variable for compliance with one of the value options:
Never loved this bullshit. Too many repetitions. Using the any union, this can be written like this:
At the core of the language is the concept of "automatic division of associations into threads" (junctive autothreading). This means that you can almost always pass the union to where only one value is expected. The code will be executed for all members of the association, and the result will be a combination of all the results obtained.
In the last example, == is run for each union element and compares it with $ var. The result of each comparison is written to the new union any, which is then evaluated in a boolean context in the if statement. In a boolean context, the union any is true if any of its members is true, so if $ var matches any of the union values, the test will pass.
This can save code and looks pretty pretty. There is another way to write the any union, which can be constructed using the | operator:
If necessary, invert the test results using a union option called none:
As you might guess, none in a Boolean context is true only if none of its elements are true.
Automatic threading also works in other cases:
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:
Of course, the regularities first, second and third should be defined. Like |, &, the operator that creates the unions, but in this case all the unions will be true if all their members are also true.
The beauty of associations is that they can be transferred to almost any function of any library, and this function does not need to know that these are associations (but it is possible to recognize them and work with them in a special way). If you have a function that makes a smart comparison of something with a value, it can be passed as a union.
There are also useful things that you can crank out with the help of associations. Is the value in the list:
Lists easily and naturally work with associations. For example:
Perl 6 supports rational fractions, which are created in a simple way - by dividing one whole into another. At first, it’s difficult to discern anything unusual here:
Converting Rat to a string occurs by presenting the number as a record with a decimal point. But Rat uses an exact internal representation, not an approximate one, which is satisfied with floating-point numbers like Num:
The easiest way to find out what happens inside the Rat number is by using the built-in .perl method. It returns a human-readable string, which through eval turns into the original object:
You can select Rat components:
Rat works with all standard numerical operations. If possible, arithmetic operations with Rat at the output also give Rat, and if impossible, Num:
Num has a fashionable method that gives out Rat of a given approximation (by default, 1e-6):
According to the specification, the numbers written in the source code as decimal are represented as Rat.
Another Perl 6 innovation is the .pick method, which allows you to select a random list item. In Perl 5, this could be done like this:
In Perl 6 it will be simpler, besides, you can immediately select several elements:
Well, let's see what kind of attack I get if I throw 10 d6s ...
It turns out that .pick matches its name - if you remove something from the list, then it no longer appears in the list. If you need to be allowed to select this item again, use the word: replace
A list is not required to contain items in any particular order. Here are the bills from the "Monopoly":
And here is an option for a deck of cards:
What does pick (*) do? We will consider a little later. For now, think about how you can improve the code for the deck of cards and make the deck class.
Although the construct is called the “switch statement,” the keyword is changed to given.
It should only be noted that not all when blocks are automatically processed - if suddenly several conditions are met at the same time, then only the first suitable block will be executed.
If your $ probability is equal to 0.80, the code will give 'Most likely', and the rest of the options will not. If you want several blocks to work, end them with the word continue (and the break / continue keywords that control the blocks are renamed to succeed / proceed).
Notice that when the expression uses both strings and numbers in the code. How does Perl 6 know how to map a given value to when when these things can be of completely different types?
In this case, two values are processed through the so-called. smart comparison that was mentioned earlier. A smart comparison, written as $ a ~~ $ b, is a trickier version of regulars. If a gap is specified, the 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 compare types. And so on. For values of type Num and Str, their equivalence is checked.
The asterisk makes smart comparisons with anything. And default means the same as when *.
And here is something unexpected for you: given and when can be used independently. While you say your “Shield?”, I will explain to you how it is:
given is a one-time cycle.
given here simply sets the topic, which is known to barls as $ _. And calls to .method methods are equivalent to calling $ _. Method
when can be used inside any block specifying $ _, explicitly or implicitly:
when demonstrates the same behavior as in the given block, i.e. skips the code remaining in the block after execution. In the example above, this means going to the next line.
Another example, with explicitly given $ _:
The independence given and when can be used in other situations. When processing a CATCH block, when given works with the $! Variable containing the last exception caught.
Options with a modified record sequence when the expression ends with an operator:
when can be embedded in given:
Since given and when make the code very clear, here is another perl-style obfuscation
Let me explain you how to work with complex numbers in Perl 6 using the Mandelbrot set as an example. Here you have higher mathematics, and beautiful pictures, and all sorts of advanced language features.
Here is the first version of the script:
Lines 3-5 specify the size of the pixel for the graph. @ * ARGS - the name of the array with command line parameters. The // operator is a new “defined”, it returns the first argument, if one is defined, and in the other case, the second. In other words, line 3 sets the height to the value of the first argument of the command line, and if it is not, then at 31. The width is set equal to the height. $ max_iterations sets the number of repetitions of the main loop, after which it is decided that the point belongs to the set (we work with symmetrical shapes, so the width should be an odd number)
Lines 7-8 specify the boundaries of the picture on the complex plane. Adding an imaginary component to a number is very simple, 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 main 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 do iterations. So the function is written - we define a loop that runs $ max_iterations times. It is known that when the module z grows larger than 2, it will not remain bounded, so we use the check $ z.abs> 2. If this happens, we exit the loop and return 1, indicating that the point should be black. If the loop goes through the maximum number of times and the value does not go beyond, we return 0, and the point becomes 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 are not specified, so any type that allows basic arithmetic operations will work here. In this script, this is Num first, then Complex.
Lines 23-24 specify the header of the PBM file.
Lines 26-30 draw a picture. $ upper-right.re is the real part of the complex number $ upper-right, and $ upper-right.im is the imaginary. The cycle goes through the real part of the gap. In the loop, we again take a subset of the imaginary part to make a list of complex values that we need to check half of this row of the picture. Then this list is 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 symmetric about the axis. Therefore, we select the last, midpoint, and expand the list so that it turns into the same list, only backwards (and with the exception of the midpoint). A loan, we pass this to join to fill the line to the end and print it.
Such an operation produces a set rotated 90 degrees from its usual position, so we get a beautiful snowman like this:

You can make this algorithm automatically parallelized through hyperoperators, but there is one catch: a hyperoperator cannot invoke the usual procedure. They only call class methods and operators. Therefore, we will tweak the Complex class so that it contains the .mandel method
The difference is that mandel is now a method, and self took the role of the $ c argument. And then instead of map ({mandel ($ _)}) we use the hyperoperator.
But if someone doesn't like to change the Complex class, you can just turn mandel into an operator.
Perl 6 supports Unicode, so you can have fun and set the operator through the snowman character.
For the latest version of Rakudo, I redid the script so that it produces a colorful picture. It is slow and eats a lot of memory, but it works stably. Here's a lot of Mandelbrot in the resolution of 1001 × 1001, which miscalculated for 14 hours, and which required 6.4 GB of memory.

According to the OOP tradition, classes deal with 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 over instances, and Roles do reuse.
What is a Role? Imagine that we are building a bunch of classes, each of which represents different types of products. Some will have common functionality and attributes. For example, we may have the role of BatteryPower.
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.
Composition takes attributes and methods from the role and copies them to the class. From now on, everything works as if attributes and methods were defined in the class itself. Unlike inheritance, where parent classes are searched at the time the methods are distributed, classes have no connection at the time the program runs, except that the class says yes in response to the question whether it plays a specific role.
Interests begin when we insert several roles into a class. Suppose we have another role, SocketPower.
The laptop can run on a wall outlet or battery, so we insert both roles.
We try compilation and nothing works. 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 providing the class with a method that decides what needs to be done.
This is the most typical role usage example, but not the only one. The role can be accepted and inserted into the object (that is, at the class level, but at the object level) through the does and but statements, and this will work just like the interfaces in Java and C #. But let's not talk about that now - I’ll better show how roles in Perl 6 deal with generalized programming, or parametric polymorphism.
Roles can take parameters, which can be types or values. For example, you can make the role that we assign to products that need to calculate shipping costs. However, we need to be able to provide other models for calculating delivery costs, so we take a class that can handle delivery costs as a parameter to the role.
Here :: Calculator in square brackets after the role name indicates 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 the shipping cost of ByDimension and ByMass. Then we can write:
When defining a role with parameters, you simply indicate 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 specify many roles with the same name, which take different types and different types of parameters.
Besides the ability to parameterize roles through square brackets, it is also possible to use the of keyword if each of the roles accepts only one parameter. Therefore, after the following declarations:
You can write like this:
It can even be summarized as follows:
The final example is just a more readable entry for Tray [Glass [MulledWine]].
Anything Anything
is a type in Perl 6 that represents everything that makes sense in a given context.
Examples:
So how does this magic work?
Some examples are simple. * at the position of a member of the expression, an Whatever object is returned, 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 met a member of an expression or an operator.
Therefore, in the record
the first * is treated as a member of the expression, the second as an operator. This example generates a code block -> $ x {$ x * 2}.
The same way
Is just a short entry for
but
just a short entry for
Whatever is useful in sorting too - for example, to sort a list by order of numbers (the prefix + means convert to numeric):
To sort the list according to the rules for strings (the prefix ~ means to convert the value to string form):
One of Perl 6’s simple and powerful ideas is introspection. For YaP, this is a mechanism by which you can ask questions about a language using the language itself. For example, object instances have methods that say which class it belongs to, methods that list the available methods, etc.
Even a procedure has a method reporting the name of this procedure:
Although this doesn’t look very meaningful, 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:
Here are some more methods for learning procedures:
The last parameter is arity, or the number of required parameters. Thanks to introspection in Perl 6 you can do previously impossible things. For example, in Perl 5, the map block takes a list of points one at a time and converts it into one or more new points from which it creates a new list. Since Perl 6 knows how many arguments are expected, it can take as much as it needs.
Another advantage is a more convenient mechanism for sorting arrays by criteria other than string comparisons. If you specify a sort procedure for an array, it usually takes two arguments — the items to compare from the array. If we wanted to sort people by their karma, we would write something like:
But. Thanks to introspection, there is another option. By passing a procedure that takes only one parameter, Perl 6 can automatically create the equivalent of a Schwartz transform.
en.wikipedia.org/wiki/Swartz Transformation
However, since we have only one parameter, $ _ is implicitly set in the procedure, so you can get rid of extra characters:
This example calls the .karma method for each element of the array once (and not twice, as for comparison in the usual case) and then sorts the array according to these results.
Another trick is the built-in type system. In the example above, I did not declare the need for numerical sorting, since perl would guess that we are using numbers. If I had to force the type of sorting, I would use + or ~:
In .min and .max methods, this is especially convenient. They also adopt a procedure to determine the sorting criteria:
This can also be written using Whatever:
Let's say we have a bunch of text that needs to be parsed. Is Perl intended for this? We specify the problem: the following text describes questions and answers:
In Perl 6, I will define Grammar for parsing. This is a special kind of namespace that contains regular expressions. We also define several named expressions to divide the parsing task into parts.
By default, spaces are ignored in grammars, and matches are searched across the line — as if the modifiers / x and / s were included in Perl 5. TOP is a regularity that is called if we look for a match across the entire grammar.
'token' is one of three identifiers used to specify the regularity, including 'regex', 'token', and 'rule'.
'regex' is a simple version, and the other two just add the options
'token' prohibits returns, and 'rule' prohibits returns and includes a literal search for spaces specified in the regular season. 'rule' we will not use.
The syntax is used to call another named regular. '^^' is used to indicate the beginning of a line, as opposed to '^', indicating the beginning of all text. Square brackets are a grouping that does not affect the array of found parts of the string, an analog (?:) In Perl 5.
The = sign assigns a name to the right side on the left side. Let's see what happens if we look for this grammar and display the search results:
We will not include the entire output of 232 lines here. Let's consider one part, questions.
.flat is used because $ match is an array contained in a scalar container. Angle brackets are equivalent to the following notation:
This shows that the object contains named items as hash values, and the repetitions are contained in an array. If we had an array of results found created by parentheses, as in Perl 5 (), we could get to its elements through the positional interface using square brackets (as when working with arrays).
The next step is to make several classes and propagate them based on the object. Class Definitions:
Creating Question objects from search results is not so difficult, but it looks ugly:
Bearing in mind that any repetition in the regular season leads to the appearance of an array in the object, we run map by the attribute, and build a Question object for each. Each entry drags an array of from, which we also use map to build a list of Question :: Answer objects. We convert the found values 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 .parse () method of the grammar. The parsing engine will then call methods with the same name that the processed regular 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 the .ast attribute (“Abstract Syntax Tree”, abstract syntax tree) of the Match object.
But all this is pretty abstract - let's look at the code. We need a class with methods named just like our three regulars:
$ / Is the traditional name for Match objects, and it is as special as $ _ - it has a special syntax for accessing attributes. Access through names or positions without a variable ($ and $ [1]) translates into access to $ / ($ / and $ / [1]). The difference is one character, but it allows you to avoid visual noise and use semantic constructions similar to $ 1, $ 2, $ 3 in Perl 5.
In the TOP method, we use the hyper-method call of the method to make a list of .ast attributes for each item in $. Wherever we call make in the action method, we define something as the .ast attribute of the returned Match object, so this is just a call to what we make on the question method.
In the 'question' method, we create a new Question object, passing all the attributes from the match object to it, and assigning the 'answer' attributes to it the list of objects obtained with each call to the regular 'answer' regular question 'question'.
In the 'answer' method, we do the same by assigning a comparison result to the 'correct' attribute to suit the 'Bool' type of the attribute.
When parsing, we make an instance of this new class and pass the object as a parameter: action to the grammar .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 is going according to plan:
And for completeness, let's add a method to 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:
Now we get the line from STDIN and extract the numbers from it:
'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 have to choose a separating character. The user can enter “1 2 3 ″,“ 1,2,3 ”or even“ 1, 2 and 3 ″. Then, through a call to 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 correctness of the answers.
We will call it for each of the questions and collect the results through the map of our new method:
The results will be something like this:
Here is the full text of the program:
Perl 6 lets you overload existing operators and define new ones. Operators are simply multiprocedures with a special name, and standard rules for multiprocedures are used to determine the desired operator variant.
A common example is the definition of a factorial operator, similar to a mathematical notation:
The first part of the definition is the syntax category (prefix, postfix, infix, circumfix or postcircumfix). After the colon are angle brackets in which the operator is written. In the case of the circumfix, two pairs of brackets are required, but for all the others one is enough, inside which there can 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 specify the order of precedence over other operators.
If you specify a replacement for an existing operator, the new definition is simply added to the set of its multi-procedures. For example, you can define your class and determine that its objects can be added with the + operator:
Of course, real-world examples are more complex and include several variables. You can specify string equality checking:
Things to avoid are prefix operator overloads: <~> (conversion to string). If you do this, you will not be able to intercept all string conversions. Instead, it's best to define your class the Str method that will do the job:
And this method will be called by the traditional ~ operator. Methods whose names coincide with types are used to convert types, so you can set the Str and Num methods to your classes in cases where this makes sense.
And since the source code for Perl 6 is written in Unicode, you can define new operators using all the richness of characters.
Lazy fruits of the Garden of Eden
Consider a design that is not often found in other languages - an iterator constructor called gather.
Historically, many barley plants have known the convenient map, grep, and sort functions:
map and grep are especially good when you learn how to chain them:
This led to the creation of the Schwartz transform, an idiom for caching in that case. when sorting is resource intensive:
One of the previous examples showed how this conversion is now built into sort:
So what about gather? This is a kind of generalization of map and grep.
gather signals that we are building a list inside the next block. Each take adds an element to it. This is how to push into an anonymous array:
It turns out that the first property of gather is use when building lists when map, grep and sort are not enough. Of course, do not reinvent them ... But the fact that this is possible, while setting its own special requirements, looks good.
An example is more convenient than map, because between iterations we need to process $ string-accumulator.
The second property of gather - although take calls must occur in the scope of the gather block, they do not have to be in the lexical area - only in the dynamic. For those who do not understand the differences, I will explain:
Here we wrap the & traverse-tree-inorder call in a gather statement. The instruction itself does not contain lexical calls to take, but the called procedure contains, and thus take inside it remembers that it is in the context of gather. This is the dynamic context.
The third property of gather: it is "lazy." In the case of tree traversal code, this means: when the @ all-nodes assignment is performed, the tree has not yet been bypassed. The crawl begins only when accessing the first element of the array, @ all-nodes [0]. And it stops when the leftmost end vertex is found. Request @ all-nodes [1] - and the tour will resume from where it ended in order 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 code inside the gather block, but only if you need its information. Interestingly, almost all arrays are lazy by default, and reading lines from a file also. map and grep can not only be created with gather, they themselves are also lazy. This behavior opens up the possibility of programming threads and using infinite arrays.
The last procedure is to solve the Hamming problem in Perl 6.
And here is the next “gibberish", which implements a cellular automaton that suddenly draws a Christmas tree.
It is strange to call grammar an essential component of language. Obviously, the syntax matters a lot - but after defining it, we just use the grammar to describe the syntax and build the parser, right?
Not in Perl 6, whose syntax is dynamic. It is useful for identifying new keywords and things not covered by the original design. More precisely, Perl 6 supports modules and applications that change the language syntax. In addition to defining new operators, the language supports the dynamic addition of macros, types of instructions, characters, etc.
A key feature of grammar is the use of proto-regular expressions. PRV allows you to combine several regulars in one category. In a more traditional grammar, we would write:
With PRV we write it like this:
We determine that it is looking for matches in any of the constructions described, but the PRV version is much easier to extend. In the first version, adding a new statement (for example, “repeat..until”) would require rewriting the entire “rule statement” declaration. But with PRV, just add one more rule:
This rule is added to candidates in the PDP. And this works in the new grammar version:
Install Rakudo
At the moment there are several incomplete implementations of Perl 6. The most complete of them is the Rakudo compiler ( download ).
Git users can create their 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 compile it from source by downloading them from github.com/rakudo/rakudo/tree/nom
There is a ready-made binary installer for Windows. Installer versions and source code are available here.
By executing the perl6 command, you will end up in a 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 the rest are system responses. The first example is a simple say statement. The second creates a rational number and requests its type (Rat). The third one takes a list of numbers from 1 to 999, filters out those that are not divisible by 3 or 5, adds them up 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 have forgotten how to use it, read perldoc. But don't go deep, just browse.
So .fmt. Here are some ways to use it to format strings and integers.
say42.fmt('%+d') # '+42'say42.fmt('%4d') # ' 42'say42.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 <101112>.fmt('%x') # 'a b c'say <123>.fmt('%02d', '; ') # '01; 02; 03'
And here is 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 issue may differ from the above. There is also .fmt for pairs, but it works the same as hashes. .fmt is a convenient tool for changing a value or an array of values and converting 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 New Year present for you as a one-line drawing of a Christmas tree:
$ perl6 -e 'say " "x 9-$_,"#"x$_*2-1 for 0..9,2 xx 3'##########################################################################################
Option for Windows (other quotation marks required):
> perl6.exe -e "say ' 'x 9-$_,'#'x$_*2-1 for 0..9,2 xx 3"
Static typing and multi subs
In Perl 5, $ scalar variables could contain either a reference or a value. The value could be anything - an integer, a string, a non-integer, a date. Flexibility at the expense of 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 type examples:
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 the procedure using the same name for different cases. Here is an example:
multi subidentify(Int $x) {
return"$x – это целое.";
}
multi subidentify(Str $x) {
returnqq<"$x" – это строка.>;
}
multi subidentify(Int $x, Str $y) {
return"Целочисленное $x и строка \"$y\".";
}
multi subidentify(Str $x, Int $y) {
return"Строка \"$x\" и целое число $y.";
}
multi subidentify(Int $x, Int $y) {
return"Два целых числа - $x и $y.";
}
multi subidentify(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 with the modules that they release to the world, a set of tests. This tradition is maintained in perl 6 through specific instructions.
The classic way to write tests in perl is to output data using the Test Anything Protocol. But you do not have to do it manually - you can use the module.
Suppose you have a factorial function:
subfac(Int $n) {
[*] 1..$n
}
It doesn't matter how it works so far - we just want to find out if it works correctly. Let's check:
use v6;
subfac(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 - Можно использовать только со строчками
More 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 verify that calling the function with an argument other than an integer results in an error.
Test output indicates that 6 tests are running, and then on each line displays the test results (ok - if passed, not ok - if failed), test number and description.
When starting a large number of tests, I don’t want to see them all in detail, but I want to see the results. The prove command does just 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 t / directory and run prove recursively on all files from the directory with the extension .t:
prove --exec perl6 -r t
If you put this line in the Makefile, you can simply type make test to run the tests.
Metaoperators
Earlier we saw an interesting implementation of the factorial function:
subfac(Int $n) {
[*] 1..$n
}
But how does it work? Perl 6 has several meta-operators that modify existing operators that are becoming more powerful. The square brackets are the reduce meta-operator, 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 a 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 the factorial, the expression [*] 1 .. $ n takes the value of the multiplied list elements from 1 to $ n.
Another metaoperator is hyper. By placing "or" (or their ASCII counterparts >> 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 pairwise adding the @a and @b elements:
@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 meta-operators, 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;
Go into hyperspace
before we continue the study metaoperatorov, lsay introduce an auxiliary function that prints out nicely formatted lists. Defining it through our you can then use it in a REPL environment:
oursublsay(@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 recording options are identical. But if their lengths differ:
> lsay (1, 2, 3, 4) <<+>> (3, 1)
[4, 3, 4, 5]
> lsay (1, 2, 3, 4) >>+<< (3, 1)
# не работает
The rule is this: what the sharp end of the hyperoperator indicates can be extended if it is shorter than what is on its other end. Renewal occurs by repeating the last element of the list. What the "blunt" end points to is not subject to renewal. All combinations are possible, for example, when only the left side (<< + <<), only the right side (>> + >>), both sides (<< + >>), or none of the sides (>> + << ) 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]
These are 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 better is not necessary. The use of hyperoperators implies that the operation can be performed in parallel, and the order of operations on the list is not fixed.
Hyperoperators work not only with built-in operators. You can define your own operator, and they will also work with it. They should work (but do not work yet) with in-place operators - for example, the instruction @a >> / = >> 2 should divide the entire array by 2. They work with multidimensional 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 loops are. A common example of using loops is a foreach loop to traverse arrays. This is exactly the kind of keyword we used in Perl 5, although you could use for, which was more reminiscent of the C style.
In Perl 6, everything is different.
Now for is used for passing through lists. foreach is no longer there, and the word loop is used for C-style. For now, we will consider only for, which is a new, flexible and powerful feature of the language:
for1, 2, 3, 4 { .say }
Immediately noticeable is the absence of parentheses around the list. Typically, in Perl 6 you need fewer brackets than in Perl 5. The default variable, like 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 without arguments - you need to write either .say or $ _. Say
Instead of a simple block, you can use a "pointed" block that allows you to specify the name of a loop variable:
for1, 2, 3, 4 -> $i { $i.say }
The “pointed” block resembles an anonymous procedure; it just does not catch exceptions. And if you write return inside such a block, then the whole procedure that called it will exit. Such blocks take more than one parameter. And what happens if you write like this:
for1, 2, 3, 4 -> $i, $j { "$i, $j".say }
At startup, you will receive:
1 2
3 4
That is, you went through the list, sorting 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're going through? Of course, you can use an array variable:
for @array { .say }
But in simple cases, we can use map instead:
@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 span operator <..>:
for1..4 { .say }
Often you need to create a list of $ n numbers starting with 0. You could write 0 .. $ n-1 or use the gap constructor 0 .. ^ $ n, but in Perl 6 there is a shorter way through the ^ prefix:
for ^4 { .say }
At the output we get:
0
1
2
3
The reasons for using C-style loops are to know which of the list items we are on right now, or need to go through several arrays at the same time. Perl 6 also has a short entry for this via the Z (zip) statement:
for @array1 Z @array2 -> $one, $two { ... }
If both arrays have the same length, $ one will go through all the elements of @ array1, and $ two will go 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 endless lists are not to your liking:
for ^@array.elems Z @array -> $index, $item { ... }
which will lead 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 the indices of the 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 { ... }
Parameter and .comb restrictions
Just as static types limit the value of a variable, so constraints allow you to control the operation of procedures and methods. In many PLs, it is necessary to pass parameters to the procedure and check the received values. With restrictions, verification can be done right at the announcement. Example: we do not need even numbers. In Perl 5, you could write this:
subvery_odd{
my $odd = shift;
unless ($odd % 2)
{
returnundef;
}
# Работаем с нечётным числом
}
In Perl 6 you can make it simpler:
subvery_odd(Int $oddwhere{$odd % 2})
{
# Работаем с нечётным числом
}
If you call very_odd with an even parameter, you get an error. For convenience, you can reload the procedure, and work with any numbers:
multi subvery_odd(Int $oddwhere{$odd % 2})
{
# Работаем с нечётным числом
}
multi subvery_odd(Int $odd) { return Bool::False; }
Parameter restrictions are conveniently used in conjunction with the .comb method. What is .comb? (comb - comb). In the case of hair and a comb, you separate the strands and lay them on your head. .comb is the opposite of .split. If the last method allows you to split the string by those elements that you do not need, then .comb separates it by 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 produces “P | e | r | l | A | d | v | e | n | t”: it takes each letter and places it in a temporary array, which is then joined via “|”. The second line acts similarly, only there the greatest possible number of letters is captured in a row, and the result is “Perl | Advent”.
But .comb is much more powerful. After you comb the string, you can manipulate the strands. If you have a string of ASCII characters, you can use hyperoperators to replace each piece with the ASCII equivalent:
say"5065726C36".comb(/<xdigit>**2/)».fmt("0x%s")».chr
# Выдаёт "P e r l 6"
You can write this through the .map method:
say"5065726C36".comb(/<xdigit>**2/).map: { chr'0x' ~ $_ } ;
# Выдаёт "P e r l 6"
As usual, there is more than one way to do something.
But the task is more complicated: I present to you the ancient Caesar cipher through the restrictions of the parameters, .comb and .map
use v6;
subrotate_one( Str $cwhere{ $c.chars == 1 }, Int $n ) {
return $c if $c !~~ /<alpha>/;
my $out = $c.ord + $n;
$out -= 26if $out > ($c eq $c.uc ?? 'Z'.ord !! 'z'.ord);
return $out.chr;
}
subrotate(Str $swhere{$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;
sayqq|"$mess" после $rotate ротаций выглядит как "{rotate($mess,$rotate)}".|;
Beautiful arguments and options
In Perl 5, working with parameters is built through @_:
subsum{
[+] @_
}
say sum 100, 20, 3; # 123
[+] - instruction from Perl 6, but if we write
my $i = 0; $i += $_ for @_; $i
this 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 necessary to check:
subgrade_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 horrified how many manual checks would have to be done. 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 like this:
subgrade_essay(Essay $essay, Int $gradewhere 0..5) {
%grades{$essay} = $grade;
}
Another thing, and without any third-party modules. Sometimes it’s convenient to set default values:
subentreat($message = 'Ну пазалуста!', $times = 1) {
say $message for ^$times;
}
These values do not have to be constants, but they can also include the previous parameters:
subxml_tag ($tag, $endtag = matching_tag($tag) ) {...}
If no default value is specified, mark the parameter as an optional question mark:
subdeactivate(PowerPlant $plant, Str $comment?) {
$plant.initiate_shutdown_sequence();
say $comment if $comment;
}
Which is especially cool, parameters can be referenced by name, and pass them in any order. I could never remember the sequence of parameters:
subdraw_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 "there will now be a named parameter", and all together will be: parameter_name ($ passed_variable). When the names of the parameters and variables coincide, you can use a short notation:
draw_line(:$x1, :$y1, :$x2, :$y2); # работает!
draw_line(:$x1, :$x2, :$y1, :$y2); # и так работает!
If the author of an API wants everyone to use named parameters, he will need to specify colons in the function declaration:
subdraw_line(:$x1, :$y1, :$x2, :$y2 ) { ... } # необязательные именованные параметры
Named parameters are optional by default. In other words, the top example is equivalent to the following:
subdraw_line(:$x1?, :$y1?, :$x2?, :$y2?) { ... } # необязательные именованные параметры
If you need to make the parameters mandatory, use an exclamation mark:
subdraw_line(:$x1!, :$y1!, :$x2!, :$y2!) { ... } # обязательные именованные параметры
Now they must be transferred.
What about a variable number of parameters? Easy: make the parameter an array preceded by an asterisk:
subsum(*@terms) {
[+] @terms
}
say sum 100, 20, 3; # 123
It turns out that if you do not set restrictions on the parameters of the future function, then it receives the default restrictions * @_. What this means is the absence of restrictions, or emulation of the behavior of Perl 5.
But an array with a star receives only parameters in a certain order. If you need to pass named parameters, use a hash:
subdetect_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, options are read-only:
subincrease_by_one($n) {
++$n
}
my $value = 5;
increase_by_one($value); # хрясь
One reason is efficiency. Optimizers like read-only variables. The second is to bring up the right habits in a programmer. Functional programming is good for both the optimizer and the soul.
For the top example to work, you need to write it like this:
subincrease_by_one($nisrw) {
++$n
}
my $value = 5;
say increase_by_one($value); # 6
Sometimes this works, but sometimes it's easier to change a copy of the parameter:
subformat_name($first, $middleiscopy, $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 “|":
sublist_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 a 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 code blocks:
subtraverse_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)
$ - parameter without restrictions.
Do not fall into the trap of trying to assign a type twice - through the type name and through the symbol:
subf(Array @a) { ... } # неправильно, если только вам не нужен массив массивовsubf( @a) { ... } # вот, что вы имели в видуsubf(Int @a) { ... } # массив целых чисел
If you read up to this point, you deserve one more one-liner:
$ perl6 -e '.fmt("%b").trans("01" => " #").say for <734043054508967647390469416144647854399310>.comb(/.**7/)'### ## #### # ## # ## # #### # # ## # ##### #### # # # ## # # # # ## ## # ## ###
Regular story
Once upon a time in a not-so-distant kingdom, a Perl 6 student named Tim worked on a simple parsing problem. His boss, Mr. C, parsed him for logs containing inventory information to make sure they only contain valid lines. Valid strings should look like this:
<номер запчасти> <количество> <цвет > <описание>
A student who is a little familiar with regulars wrote a beautiful regular for determining the permissible lines. The code looked like this:
nextunless $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 season, ^^ 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 $$ end of line. In Perl 6, these characters can be separated by spaces to improve readability. And everything was wonderful.
But then Mr. C decided that it would be nice to extract information from the logs, and not just check it. Tim thought there were no problems and just add exciting brackets. So he did:
nextunless $line ~~ / ^^ (\d+) \s+ (\d+) \s+ (\S+) \s+ (\N*) $$ /
After the match, the contents of each pair of brackets is accessible 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>)
Moreover, the description could have any number of characters with spaces. Hedgehogs, thought Tim, the task has just become very complicated! But Tim knew where to ask for advice. He quickly went to irc.freenode.org in channel # perl6 and asked there. Someone advised to name the parts of the regular season to make it easier to work with them and then use alternation to catch all possible alternatives.
At first Tim tried to name the parts of the regular season. After looking at the description of regulars in Perl 6, Tim discovered that he could make such an entry:
nextunless $line ~~ / ^^ $<product>=(\d+) \s+ $<quantity>=(\d+) \s+ $<color>=(\S+) \s+ $<description>=(\N*) $$ /
And then, after finding, the pieces of the regular season are available through the match object or the variables $, $, $ and $. It was simple, and Tim perked up. He then added alternation so that both line options could pass the test:
nextunless $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 her with grouping square brackets. These brackets separate part of the regular, like round ones, but do not return the separated parts to $ 0 variables, etc. Since he needed to catch parentheses in the file, Tim took advantage of another convenient feature of Perl 6: what is enclosed in quotation marks is looked up in the text as it is.
Tim was encouraged. He showed the code to Mr. C, and he was also inspired! “Well done, Tim!” Said Mr. C. Everyone was happy, and Tim shone with pride.
However, then he critically looked at his work. For some lines, the color is set to “(color)” or “(color)” or “(color)”. As a result, the regularity assigned such colors to the description, but did not set the $ variable at all. That was unacceptable. Tim rewrote the regular season, adding \ s * there too:
nextunless $line ~~ / ^^
$<product>=(\d+) \s+ $<quantity>=(\d+) \s+
[
| $<description>=(\N*) \s+ '(' \s* $<color>=(\S+) \s* ')'
| $<color>=(\S+) \s+ $<description>=(\N*)
]
$$
/
It worked, but the regular 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 in grammar? After all, you are practically doing this, assigning each variable its own variable. ” Shield? Tim thought. He had no idea what PerlJam was talking about. After a short conversation, Tim seemed to understand what was meant, thanked the user, and sat down to write code. This time, the regularity disappeared and turned into a grammar. Here's what she looked like:
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>
]
$$
}
}
# ... а затем, в том месте кода, где проверяется совпадение:nextunless Inventory.parse($line);
Well, Tim thought, this time everything is organized quite well.
Each of the past variables has become its own regularity within the grammar. In the Perl 6 Regular, named regulars are added to the test by enclosing them in angle brackets. The special TOP 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 a named variable.
And although there is no limit to perfection, Tim and Mr. C were very pleased with the result.
The end!
Classes, Attributes, Methods, and Others
How to write a class in the 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, the class is somewhat similar to package, but “out of the box” provides a bunch of semantic possibilities.
Then we used the has keyword, declaring an attribute that has an accessor method. The point between $ and name is a tweedle that reports on the features of accessing the variable. Twigil-dot means “attribute + accessor”. More options:
has $!name; # приватный, доступный только из класса
has $.name is rw; # акцессор только для чтения
The method is then declared via the method keyword. A method is like a procedure, only with its own entry in the class method table. It is available for calling through $ 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 like this:
my $fido = Dog.new(name =>'Fido');
say $fido.name; # Fido
$fido.bark(3); # w00f! w00f! w00f!
Method calls are made with a dot instead of an arrow in Perl 5. It is 50% shorter and familiar to programmers in 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 also a delegation:
class DogWalker {
has $.name;
has Dog $.dog handles (dog_name =>'name');
}
my $bob = DogWalker.new(name =>'Bob', dog => $fido);
say $bob.name; # Bobsay $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 through 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 that represents 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 simply display its name.
Meta-programming enthusiasts who want to extend Perl 6 syntax will be thrilled to learn that using the method keyword actually causes add_method to be called 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 envisioned.
Modules and Export
To create a library in Perl 6, you need to use the module keyword:
module Fancy::Utilities {
sublolgreet($who) {
say"O HAI " ~ uc $who;
}
}
Put this 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 convenient. As in Perl 5, it is possible to indicate that some things should be available in the scope of the code that loads this module. There is a syntax for this:
# Utilities.pm
module Fancy::Utilities {
sublolgreet($who) isexport{
say"O HAI " ~ uc $who;
}
}
# foo.pluse Fancy::Utilities;
lolgreet('Jnthn');
Marked “is export” characters are exported by default. You can also note that characters are exported as part of a named group:
module Fancy::Utilities {
sublolgreet($who) isexport(:lolcat, :greet) {
say"O HAI " ~ uc $who;
}
subnicegreet($who) isexport(:greet, :DEFAULT) {
say"Good morning, $who!"; # Always morning?
}
subshortgreetisexport(:greet) {
say"Hi!";
}
sublolrequest($item) isexport(:lolcat) {
say"I CAN HAZ A {uc $item}?";
}
}
These tags can be used in the loading code to choose what to import:
use Fancy::Utilities; # получаем только DEFAULTsuse Fancy::Utilities :greet, :lolcat;
use Fancy::Utilities :ALL; # берём всё, что можно экспортировать
Multi-procedures are exported by default, they can only be given labels as desired:
multi subgreet(Str $who) { say"Good morning, $who!" }
multi subgreet() { say"Hi!" }
multi subgreet(Lolcat $who) { say"O HAI " ~ $who.name }
Classes are a specialization of modules, so you can also export something from them. 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 a library by name.
Junctions
Among the new features of Perl 6, I like associations most of all. I do not represent all the options for their use, but I know a few convenient tricks.
Unions are variables that can contain multiple values at once. It sounds strange, but let's look at an example. Suppose you need to check a variable for compliance with one of the value options:
if $var == 3 || $var == 5 || $var == 7 { ... }
Never loved this bullshit. Too many repetitions. Using the any union, this can be written like this:
if $var == any(3, 5, 7) { ... }
At the core of the language is the concept of "automatic division of associations into threads" (junctive autothreading). This means that you can almost always pass the union to where only one value is expected. The code will be executed for all members of the association, and the result will be a combination of all the results obtained.
In the last example, == is run for each union element and compares it with $ var. The result of each comparison is written to the new union any, which is then evaluated in a boolean context in the if statement. In a boolean context, the union any is true if any of its members is true, so if $ var matches any of the union values, the test will pass.
This can save code and looks pretty pretty. There is another way to write the any union, which can be constructed using the | operator:
if $var == 3|5|7 { ... }
If necessary, invert the test results using a union option called none:
if $var == none(3, 5, 7) { ... }
As you might guess, none in a Boolean context is true only if none of its elements are true.
Automatic threading also 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 regularities first, second and third should be defined. Like |, &, the operator that creates the unions, but in this case all the unions will be true if all their members are also true.
The beauty of associations is that they can be transferred to almost any function of any library, and this function does not need to know that these are associations (but it is possible to recognize them and work with them in a special way). If you have a function that makes a smart comparison of something with a value, it can be passed as a union.
There are also useful things that you can crank out with the help of associations. Is the value in the list:
any(@list) == $value
Lists easily and naturally work with associations. 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 - by dividing one whole into another. At first, it’s difficult to discern anything unusual here:
> say (3/7).WHAT
Rat()
> say 3/7
0.428571428571429
Converting Rat to a string occurs by presenting the number as a record with a decimal point. But Rat uses an exact internal representation, not an approximate one, which is satisfied with floating-point numbers 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 happens inside the Rat number is by 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]
Rat works with all standard numerical operations. If possible, arithmetic operations with Rat at the output also give Rat, and if 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 fashionable method that gives out Rat of a 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 as decimal are represented as 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, this could be done 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 simpler, besides, you can immediately select several elements:
my @dice = 1..6;
say @dice.pick(2).join(" ");
> 34
Well, let's see what kind of attack I get if I throw 10 d6s ...
my @dice = 1..6;
say @dice.pick(10).join(" ");
> 531426
It turns out that .pick matches its name - if you remove something from the list, then it no longer appears in the list. If you need to be allowed to select this item again, use the word: replace
my @dice = 1..6;
say @dice.pick(10, :replace).join(" ");
> 4156433511
A list is not required to contain items in any particular order. Here are the bills from the "Monopoly":
my @dice = <15102050100500>;
say @dice.pick(10, :replace).join(" ");
> 2050100500500102055020
And here is 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 23456789 T J Q K> -> $rank
{
for < > -> $suit
{
@deck.push(Card.new(:$rank, :$suit));
}
}
# Shuffle the cards.
@deck .= pick(*);
say @deck.Str;
What does pick (*) do? We will consider a little later. For now, think about how you can improve the code for the deck of cards and make the deck class.
Good old switch
Although the construct is called the “switch statement,” the keyword is changed to given.
given $weather {
when'sunny' { say'Отлично! ' }
when'cloudy' { say'Ну блин. ' }
when'rainy' { say'Где ж мой зонт? ' }
when'snowy' { say'Йиха! ' }
default { say'Ну всё, как обычно.' }
}
It should only be noted that not all when blocks are automatically processed - if suddenly several conditions are met at the same time, then only the first suitable block will be executed.
given $probability {
when1.00 { say'Однозначно' }
when * > 0.75 { say'Скорее всего' }
when * > 0.50 { say'Вероятно' }
when * > 0.25 { say'Маловероятно' }
when * > 0.00 { say'Вообще навряд ли' }
when0.00 { say'Ни за что' }
}
If your $ probability is equal to 0.80, the code will give 'Most likely', and the rest of the options will not. If you want several blocks to work, end them with the word continue (and the break / continue keywords that control the blocks are renamed to succeed / proceed).
Notice that when the expression uses both strings and numbers in the code. How does Perl 6 know how to map a given value to when when these things can be of completely different types?
In this case, two values are processed through the so-called. smart comparison that was mentioned earlier. A smart comparison, written as $ a ~~ $ b, is a trickier version of regulars. If a gap is specified, the 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 compare types. And so on. For values of type Num and Str, their equivalence is checked.
The asterisk makes smart comparisons with anything. And default means the same as when *.
And here is something unexpected for you: given and when can be used independently. While you say your “Shield?”, I will explain to you how it is:
given is a one-time cycle.
given $punch-card {
.bend;
.fold;
.mutilate;
}
given here simply sets the topic, which is known to barls as $ _. And calls to .method methods are equivalent to calling $ _. Method
when can be used inside any block specifying $ _, 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 the code remaining in the block after execution. In the example above, this means going to the next line.
Another example, with explicitly given $ _:
subfib(Int $_) {
when * < 2 { 1 }
default { fib($_ - 1) + fib($_ - 2) }
}
The independence given and when can be used in other situations. When processing a CATCH block, when given works with the $! Variable containing the last exception caught.
Options with a modified record sequence when the expression ends with an operator:
say .[0] + .[1] + .[2] given @list;
say'Ух ты ж, да тут полно гласных!'when /^ <[аеиоюуэы]>+ $/;
when can be embedded in given:
say'Бах!'when /призрак/ given $castle;
Since given and when make the code very clear, here is another perl-style 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}'
G C
TA
C G
G C
C G
G C
T A
CG
CG
C G
T A
T A
T A
C G
TA
T A
T A
A T
C G
G C
Making snowmen
Let me explain you how to work with complex numbers in Perl 6 using the Mandelbrot set as an example. Here you have higher mathematics, and beautiful pictures, and all sorts of advanced language features.
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;
submandel(Complex $c) {
my $z = 0i;
for ^$max_iterations {
$z = $z * $z + $c;
return1if ($z.abs > 2);
}
return0;
}
subsubdivide($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 specify the size of the pixel for the graph. @ * ARGS - the name of the array with command line parameters. The // operator is a new “defined”, it returns the first argument, if one is defined, and in the other case, the second. In other words, line 3 sets the height to the value of the first argument of the command line, and if it is not, then at 31. The width is set equal to the height. $ max_iterations sets the number of repetitions of the main loop, after which it is decided that the point belongs to the set (we work with symmetrical shapes, so the width should be an odd number)
Lines 7-8 specify the boundaries of the picture on the complex plane. Adding an imaginary component to a number is very simple, 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 main 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 do iterations. So the function is written - we define a loop that runs $ max_iterations times. It is known that when the module z grows larger than 2, it will not remain bounded, so we use the check $ z.abs> 2. If this happens, we exit the loop and return 1, indicating that the point should be black. If the loop goes through the maximum number of times and the value does not go beyond, we return 0, and the point becomes 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 are not specified, so any type that allows basic arithmetic operations will work here. In this script, this is Num first, then Complex.
Lines 23-24 specify the header of the PBM file.
Lines 26-30 draw a picture. $ upper-right.re is the real part of the complex number $ upper-right, and $ upper-right.im is the imaginary. The cycle goes through the real part of the gap. In the loop, we again take a subset of the imaginary part to make a list of complex values that we need to check half of this row of the picture. Then this list is 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 symmetric about the axis. Therefore, we select the last, midpoint, and expand the list so that it turns into the same list, only backwards (and with the exception of the midpoint). A loan, we pass this to join to fill the line to the end and print it.
Such an operation produces a set rotated 90 degrees from its usual position, so we get a beautiful snowman like this:

You can make this algorithm automatically parallelized through hyperoperators, but there is one catch: a hyperoperator cannot invoke the usual 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;
return1if ($z.abs > 2);
}
return0;
}
}
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 self took the role of the $ c argument. And then instead of map ({mandel ($ _)}) we use the hyperoperator.
But if someone doesn't like to change the Complex class, you can just turn mandel into an operator.
subpostfix:<>(Complex $c) {
my $z = 0i;
for ^$max_iterations {
$z = $z * $z + $c;
return1if ($z.abs > 2);
}
return0;
}
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 character.
For the latest version of Rakudo, I redid the script so that it produces a colorful picture. It is slow and eats a lot of memory, but it works stably. Here's a lot of Mandelbrot in the resolution of 1001 × 1001, which miscalculated for 14 hours, and which required 6.4 GB of memory.

Roles
According to the OOP tradition, classes deal with 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 over instances, and Roles do reuse.
What is a Role? Imagine that we are building a bunch of classes, each of which represents different types of products. 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;
}
Composition takes attributes and methods from the role and copies them to the class. From now on, everything works as if attributes and methods were defined in the class itself. Unlike inheritance, where parent classes are searched at the time the methods are distributed, classes have no connection at the time the program runs, except that the class says yes in response to the question whether it plays a specific role.
Interests begin when we insert several roles into a 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 run on a wall outlet or battery, so we insert both roles.
class Laptop does BatteryPower does SocketPower {
}
We try compilation and nothing works. 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 providing the class with a method that decides what needs to be done.
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 role usage example, but not the only one. The role can be accepted and inserted into the object (that is, at the class level, but at the object level) through the does and but statements, and this will work just like the interfaces in Java and C #. But let's not talk about that now - I’ll better show how roles in Perl 6 deal with generalized programming, or parametric polymorphism.
Roles can take parameters, which can be types or values. For example, you can make the role that we assign to products that need to calculate shipping costs. However, we need to be able to provide other models for calculating delivery costs, so we take a class that can handle delivery costs as a parameter to the 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 role name indicates 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 the shipping cost of 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 indicate 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 specify many roles with the same name, which take different types and different types of parameters.
Besides the ability to parameterize roles through square brackets, it is also possible to use the of keyword if each of the roles accepts only one parameter. Therefore, after the following declarations:
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 summarized as follows:
role Tray[::ItemType] { }
my Tray of Glass of MulledWine $valuable;
The final example is just a more readable entry for Tray [Glass [MulledWine]].
Anything Anything
is a type in Perl 6 that represents everything that makes sense in a given context.
Examples:
1..* # бесконечный промежутокmy @x = <a b c d 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, an Whatever object is returned, 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 met a member of an 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 code block -> $ x {$ x * 2}.
my $x = * * 2;
say $x(4); # says 8
The same way
say @x.map: * ~ 'A';
Is just a short entry for
say @x.map: -> $x { $x ~ 'A' };
but
say @x.map: *.succ;
just a short entry for
say @x.map: -> $x { $x.succ };
Whatever is useful in sorting too - for example, to sort a list by order of numbers (the prefix + means convert to numeric):
@list.sort: +*
To sort the list according to the rules for strings (the prefix ~ means to convert the value to string form):
@list.sort: ~*
Little tricks
One of Perl 6’s simple and powerful ideas is introspection. For YaP, this is a mechanism by which you can ask questions about a language using the language itself. For example, object instances have methods that say which class it belongs to, methods that list the available methods, etc.
Even a procedure has a method reporting the name of this procedure:
subfoo (Int $i, @stuff, $blah = 5) { ... }
say &foo.name; # выводит "foo"
Although this doesn’t look very meaningful, 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 more methods for learning 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 previously impossible things. For example, in Perl 5, the map block takes a list of points one at a time and converts it into one or more new points from which it creates a new list. Since Perl 6 knows how many arguments are expected, it can take as much as it needs.
my @foo = map -> $x, $y { ... }, @bar; # брать по две штуки из @bar для создания @foomy @coords = map -> $x, $y, $z { ... }, @numbers; # брать из @numbers по три за раз
Another advantage is a more convenient mechanism for sorting arrays by criteria other than string comparisons. If you specify a sort procedure for an array, it usually takes two arguments — the items to compare from the array. If we wanted to sort people by their karma, we would write something like:
#!/usr/bin/perl6use 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 takes only one parameter, Perl 6 can automatically create the equivalent of a Schwartz transform.
en.wikipedia.org/wiki/Swartz Transformation
.say for @people.sort: { $^a.karma };
However, since we have only one parameter, $ _ is implicitly set 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 (and not twice, as for comparison in the usual case) and then sorts the array according to these results.
Another trick is the built-in type system. In the example above, I did not declare the need for numerical sorting, since perl would guess that we are using numbers. If I had to force the type of sorting, I would use + or ~:
.say for @people.sort: { +.karma }; # числовая
.say for @people.sort: { ~.karma }; # строковая
In .min and .max methods, this is especially convenient. They also adopt a procedure to determine 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 Actions
Let's say we have a bunch of text that needs to be parsed. Is Perl intended for this? We specify the problem: the following text describes questions and answers:
pickmany: Что из перечисленного является едой?
ac: Рис
ac: Апельсин
ac: Гриб
ai: Ботинки
pickone: Что из перечисленного является цветом?
ac: Апельсиновый
ai: Ботинки
ai: Грибы
ai: Рис
In Perl 6, I will define Grammar for parsing. This is a special kind of namespace that contains regular expressions. We also define 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, spaces are ignored in grammars, and matches are searched across the line — as if the modifiers / x and / s were included in Perl 5. TOP is a regularity that is called if we look for a match across the entire grammar.
'token' is one of three identifiers used to specify the regularity, including 'regex', 'token', and 'rule'.
'regex' is a simple version, and the other two just add the options
'token' prohibits returns, and 'rule' prohibits returns and includes a literal search for spaces specified in the regular season. 'rule' we will not use.
The syntax is used to call another named regular. '^^' is used to indicate the beginning of a line, as opposed to '^', indicating the beginning of all text. Square brackets are a grouping that does not affect the array of found parts of the string, an analog (?:) In Perl 5.
The = sign assigns a name to the right side on the left side. Let's see what happens if we look 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 entire output of 232 lines here. Let's 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 notation:
# Вывести вопросfor $match{'question'}.flat -> $q {
say $q{'header'}{'text'};
}
This shows that the object contains named items as hash values, and the repetitions are contained in an array. If we had an array of results found created by parentheses, as in Perl 5 (), we could get to its elements through the positional interface using square brackets (as when working with arrays).
The next step is to make several classes and propagate 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 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 season leads to the appearance of an array in the object, we run map by the attribute, and build a Question object for each. Each entry drags an array of from, which we also use map to build a list of Question :: Answer objects. We convert the found values 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 .parse () method of the grammar. The parsing engine will then call methods with the same name that the processed regular 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 the .ast attribute (“Abstract Syntax Tree”, abstract syntax tree) of the Match object.
But all this is pretty abstract - let's look at the code. We need a class with methods named just like our three regulars:
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>,
);
}
}
$ / Is the traditional name for Match objects, and it is as special as $ _ - it has a special syntax for accessing attributes. Access through names or positions without a variable ($ and $ [1]) translates into access to $ / ($ / and $ / [1]). The difference is one character, but it allows you to avoid visual noise and use semantic constructions similar to $ 1, $ 2, $ 3 in Perl 5.
In the TOP method, we use the hyper-method call of the method to make a list of .ast attributes for each item in $. Wherever we call make in the action method, we define something as the .ast attribute of the returned Match object, so this is just a call to what we make on the question method.
In the 'question' method, we create a new Question object, passing all the attributes from the match object to it, and assigning the 'answer' attributes to it the list of objects obtained with each call to the regular 'answer' regular question 'question'.
In the 'answer' method, we do the same by assigning a comparison result to the 'correct' attribute to suit the 'Bool' type of the attribute.
When parsing, we make an instance of this new class and pass the object as a parameter: action to the grammar .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 is going 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 to 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 get the line 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 have to choose a separating character. The user can enter “1 2 3 ″,“ 1,2,3 ”or even“ 1, 2 and 3 ″. Then, through a call to 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 correctness of the answers.
my @correct = @.answers.kv.map({ $^value.correct ?? $^key !! () });
if @correct ~~ @answers {
say"Да, всё верно!";
return1;
}
else {
say"Ну вот, ошибочка вышла";
return0;
}
}
We will 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" Да, всё верно!";
return1;
} else {
say" Ну вот, ошибочка вышла";
return0;
}
}
}
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 Overload
Perl 6 lets you overload existing operators and define new ones. Operators are simply multiprocedures with a special name, and standard rules for multiprocedures are used to determine the desired operator variant.
A common example is the definition of a factorial operator, similar to a mathematical notation:
multi subpostfix:<!>(Int $n) {
[*] 1..$n;
}
say3!;
The first part of the definition is the syntax category (prefix, postfix, infix, circumfix or postcircumfix). After the colon are angle brackets in which the operator is written. In the case of the circumfix, two pairs of brackets are required, but for all the others one is enough, inside which there can 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 specify the order of precedence over other operators.
If you specify a replacement for an existing operator, the new definition is simply added to the set of its multi-procedures. For example, you can define your class and determine that its objects can be added with the + operator:
class PieceOfString {
has Int $.length;
}
multi subinfix:<+>(PieceOfString $lhs, PieceOfString $rhs) {
PieceOfString.new(:length($lhs.length + $rhs.length));
}
Of course, real-world examples are more complex and include several variables. You can specify string equality checking:
multi subinfix:<==>(PieceOfString $lhs, PieceOfString $rhs --> Bool) {
$lhs.length == $rhs.length;
}
Things to avoid are prefix operator overloads: <~> (conversion to string). If you do this, you will not be able to intercept all string conversions. Instead, it's best to define your class the Str method that 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 coincide with types are used to convert types, so you can set the Str and Num methods to your classes in cases where this makes sense.
And since the source code for Perl 6 is written in Unicode, you can define new operators using all the richness of characters.
Lazy fruits of the Garden of Eden
Consider a design that is not often found in other languages - an iterator constructor called gather.
Historically, many barley plants have known the convenient map, grep, and sort functions:
my @squares = map { $_ * $_ }, @numbers;
my @primes = grep { is-prime($_) }, @numbers;
map and grep are especially good when you learn how to chain them:
my @children-of-single-moms =
map { .children },
grep { !.is-married },
grep { .gender == FEMALE }, @citizens;
This led to the creation of the Schwartz transform, 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;
One of the previous examples showed how this conversion is now built into sort:
my @files-by-modification-date =
sort { $_ ~~ :M },
@files;
So what about gather? This is a kind of generalization of map and grep.
submymap(&transform, @list) {
gather for @list {
take transform($_);
}
};
submygrep(&condition, @list) {
gather for @list {
take $_ if condition($_);
}
};
gather signals that we are building a list inside the next block. Each take adds an element to it. This is how to push into an anonymous array:
my @result = gather { take $_ for5..7 }; # это -my @result;
push @result, $_ for5..7; # эквивалент этого
It turns out that the first property of gather is use when building lists when map, grep and sort are not enough. Of course, do not reinvent them ... But the fact that this is possible, while setting its own special requirements, looks good.
subincremental-concat(@list) {
my $string-accumulator = "";
gather for @list {
take ~($string-accumulator ~= $_);
}
};
say incremental-concat(<a b c>).perl; # ["a", "ab", "abc"]
An example is more convenient than map, because between iterations we need to process $ string-accumulator.
The second property of gather - although take calls must occur in the scope of the gather block, they do not have to be in the lexical area - only in the dynamic. For those who do not understand the differences, I will explain:
subtraverse-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 a gather statement. The instruction itself does not contain lexical calls to take, but the called procedure contains, and thus take inside it remembers that it is in the context of gather. This is the dynamic context.
The third property of gather: it is "lazy." In the case of tree traversal code, this means: when the @ all-nodes assignment is performed, the tree has not yet been bypassed. The crawl begins only when accessing the first element of the array, @ all-nodes [0]. And it stops when the leftmost end vertex is found. Request @ all-nodes [1] - and the tour will resume from where it ended in order 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 code inside the gather block, but only if you need its information. Interestingly, almost all arrays are lazy by default, and reading lines from a file also. map and grep can not only be created with gather, they themselves are also lazy. This behavior opens up the possibility 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;
subenumerate-positive-rationals() { # хотя и с дублями
take 1;
for1..Inf -> $total {
for1..^$total Z reverse(1..^$total) -> $numerator, $denominator {
take $numerator / $denominator;
}
}
}
subenumerate-all-rationals() {
map { $_, -$_ }, enumerate-positive-rationals();
}
subfibonacci() {
gather {
take 0;
my ($last, $this) = 0, 1;
loop { # infinitely!
take $this;
($last, $this) = $this, $last + $this;
}
}
}
say fibonacci[10]; # 55# Объединение двух массивов, которые могут быть бесконечнымиsubmerge(@a, @b) {
!@a && !@b ?? () !!
!@a ?? @b !!
!@b ?? @a !!
(@a[0] < @b[0] ?? @a.shift !! @b.shift, merge(@a, @b))
}
subhamming-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 to solve the Hamming problem in Perl 6.
And here is the next “gibberish", which 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
Perl 6 Standard Grammar
It is strange to call grammar an essential component of language. Obviously, the syntax matters a lot - but after defining it, we just use the grammar to describe the syntax and build the parser, right?
Not in Perl 6, whose syntax is dynamic. It is useful for identifying new keywords and things not covered by the original design. More precisely, Perl 6 supports modules and applications that change the language syntax. In addition to defining new operators, the language supports the dynamic addition of macros, types of instructions, characters, etc.
A key feature of grammar is the use of proto-regular expressions. PRV allows you to combine several regulars in one category. In a more traditional grammar, we would write:
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> }
With PRV we write it like this:
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 constructions described, but the PRV version is much easier to extend. In the first version, adding a new statement (for example, “repeat..until”) would require rewriting the entire “rule statement” declaration. But with PRV, just add one more rule:
rule statement:sym<repeat> { 'repeat' <stmt> 'until' <expr> }
This rule is added to candidates in the PDP. And this works in the new grammar version:
grammar MyNe
Also popular now:
-
V-Cast # 01 Video review of Android 2.1 Eclair on Samsung i5700 Spica
-
Enterprise Content Management - how to make the right decisions quickly? / IBM Blog
-
Alternate / Proxy Design Pattern
-
Webics - Virtual World for Children
-
Sysadmin scarf
-
Multitasking in Windows Phone 7 Series
-
News about the Skolkovo project
-
Debugging a JMeter Test Plan
-
The first Android smartphone with WiMAX announced - HTC Evo 4G
-
Test stitch speed panoramas MS ICE vs Autopano Giga 2.0.3 and Hugin 0.7.0