rakulang, dartlang, nimlang, golang, rustlang, lang lang no see

Raku 中的特性(二)

焉知非鱼

Features in Raku

Set #

my $keywords = set <if for unless while>; # create a set

sub has-keyword(*@words) {
    for @words -> $word {
        # 依次检查数组中的元素是否属于集合 $keywords
        return True if $word (elem) $keywords;
    }
    False;
}

say has-keyword 'not', 'one', 'here';  # False
say has-keyword 'but', 'here', 'for';  # True

Series Operator #

my @a=<A G C T>;
my $x=@a;

for 1 ... * -> $a {  
  (( [X~] $x xx $a )).join(',').say;
  last if $a == 4;
}

倒序的 range:

for 10 ... 0 {
    .say;
}

flip/plop #

my $file = open 'flip_flop.txt';

for $file.lines -> $line {
    say $line if !($line ~~ m/^\;/ ff $line ~~ m/^\"/);
}

$line ~~ m/^\;/ ff $line ~~ m/^\"/ 过滤掉 ;" 之间的内容, 再对它进行取反操作就是过滤后剩下的文本。

flip_flop.txt 内容如下:

; next is some lines to skip,include this line
fuck fuck fuck
dam dam dam
mie mie mie
" next is subject
There is more than one way to do it
                                -- Larry Wall

We hope Raku is wrote by the hole Socfilia
                                -- Larry Wall
; next is some lines to skip,include this line
fuck fuck fuck
dam dam dam
mie mie mie
" next is subject
programming is hard,Let's go shopping
                               -- Larry Wall
Ruby is Another Raku
                               -- Larry Wall

输出:

There is more than one way to do it
                                -- Larry Wall
We hope Raku is wrote by the hole Socfilia
                                -- Larry Wall
programming is hard,Let's go shopping
                               -- Larry Wall
Ruby is Another Raku
                               -- Larry Wall

ff 操作符左右两侧的 ^ 表示排除:

for 1..20 {.say if $_==9 ff $_==16}
say '-' x 10;
for 1..20 {.say if $_==9 ^ff $_==16}
say '-' x 10;
for 1..20 {.say if $_==9 ff^ $_==16}
say '-' x 10;
for 1..20 {.say if $_==9 ^ff^ $_==16}

输出:

9
10
11
12
13
14
15
16
----------
10
11
12
13
14
15
16
----------
9
10
11
12
13
14
15
----------
10
11
12
13
14
15

Grammars #

解析 CSV:

grammar CSV {
    token TOP { [ <line> \n? ]+ }
    token line {
        ^^            # Beginning of a line
        <value>* % \, # Any number of <value>s with commas in `between` them
        $$            # End of a line
    }
    token value {
        [
        | <-[",\n]>     # Anything not a double quote, comma or newline
        | <quoted-text> # Or some quoted text
        ]*              # Any number of times
    }
    token quoted-text {
        \"
        [
        | <-["\\]> # Anything not a " or \
        | '\"'     # Or \", an escaped quotation mark
        ]*         # Any number of times
        \"
    }
}
# method parse($str, :$rule = 'TOP', :$actions) returns Match:D
say "Valid CSV file!" if CSV.parse( q:to/EOCSV/ );
    Year,Make,Model,Length
    1997,Ford,E350,2.34
    2000,Mercury,Cougar,2.38
    EOCSV

say CSV.parse( q:to/EOCSV/, 'line', :$actions );
    Year,Make,Model,Length
    1997,Ford,E350,2.34
    2000,Mercury,Cougar,2.38
    EOCSV

解析天气数据:

grammar StationDataParser {
    token TOP          { ^ <keyval>+ <observations> $             }
    token keyval       { $<key>=[<-[=]>+] '=' \h* $<val>=[\N+] \n }
    token observations { 'Obs:' \h* \n <observation>+             }
    token observation  { $<year>=[\d+] \h* <temp>+ %% [\h*] \n    }
    token temp         { '-'? \d+ \. \d+                          }
}

class StationData {
    has $.name;
    has $.country;
    has @.data;

    submethod BUILD(:%info (:Name($!name), :Country($!country), *%), :@!data) {
    }
}

class StationDataActions {
    method TOP($/) {
        make StationData.new(
            info => $<keyval>.map(*.ast).hash,
            data => $<observations>.ast
        );
    }

    method keyval($/) {
        make ~$<key> => ~$<val>;
    }
    method observations($/) {
        make $<observation>.map(*.ast).grep(*.value.none <= -99);
    }
    method observation($/) {
        make +$<year> => $<temp>.map(*.Num);
    }
}

say StationDataParser.parse( q:to/EOCSV/, :actions(StationDataActions)).ast
Name= Jan Mayen
Country= NORWAY
Lat=   70.9
Long=    8.7
Height= 10
Start year= 1921
End year= 2009
Obs:
1921 -4.4 -7.1 -6.8 -4.3 -0.8  2.2  4.7  5.8  2.7 -2.0 -2.1 -4.0  
1922 -0.9 -1.7 -6.2 -3.7 -1.6  2.9  4.8  6.3  2.7 -0.2 -3.8 -2.6  
2008 -2.8 -2.7 -4.6 -1.8  1.1  3.3  6.1  6.9  5.8  1.2 -3.5 -0.8  
2009 -2.3 -5.3 -3.2 -1.6  2.0  2.9  6.7  7.2  3.8  0.6 -0.3 -1.3
EOCSV

Raku Examples #

  • 1、生成8位随机密码
my  @char_set = (0..9, 'a'..'z', 'A'..'Z','~','!','@','#','$','%','^','&','*');
say @char_set.pick(8).join("") # 不重复的8位密码

say @char_set.roll(8).join("") # 可以重复
  • 2、打印前5个数字
.say for 1..10[^5]
.say for 1,2,3,4 ... [^10]  # 这个会无限循环
  • 3、排序

  • 3.1 按数值排序

my %hash = 'Perl' => 100,
           'Python' => 100,
           'Go' => 100,
           'CMD' => 20,
           'Php' => 80,
           'Java' => 85;

%hash.values.sort;
%hash.values.sort(-*);
  • 3.2 按分数排序散列:
my %hash = 'Perl' => 80,
         'Python' => 100,
             'Go' => 95,
            'CMD' => 20,
            'Php' => 80,
           'Java' => 85;

for %hash.sort({-.value}).hash.keys -> $key {
    say $key, "\t", %hash{"$key"}
}

输出:

Python	100
Go	95
Java	85
Perl	80
Php	80
CMD	20
('xx'..'zz').classify(*.substr(1))<z>; # xz yz zz

加密:

sub rot13 { $^s.trans('a..z' => 'n..za..m', 'A..Z' => 'N..ZA..M') }
  • 4、求 1! + 2! + 3! + 4! +5! + 6! +7! +8! +9! +10!
multi sub postfix:<!>(Int $x){ [*] 1..$x }
say [+] 1!,2!,3!,4!,5!,6!,7!,8!,9!,10! # 4037913
  • 5、列出对象所有可用的方法

使用元对象协议, 即 对象名.^methods

> "JZY".^methods

BUILD Int Num chomp chop substr pred succ match ords lines samecase samespace tr im-leading trim-trailing trim words encode wordcase trans indent codes path WHIC H Bool Str Stringy DUMP ACCEPTS Numeric gist perl comb subst split

  • 6、 匿名子例程
my $x = sub($a){ $a+2 };say $x($_) for 1..4
my $x = -> $a { $a+2 };say $x($_) for 1..4
my $x = * + 2;say $x($_) for 1..4
  • 7、字符串翻转与分割
> 1223.flip
3221
> 'abcd'.flip
dcba
> 1234.comb
1 2 3 4
> 1234.comb(/./)
1 2 3 4
> 'abcd'.comb
a b c d
  • 8、有这么一个四位数 A,其个位数相加得到 B,将 B 乘以 B 的反转数后得到 A,请求出这个数字。

举例, 1458 就符合这个条件,1+4+5+8 = 18, 18 * 81 =1458

请找出另一个符合上面条件的四位数。

(^37).map: {
  my $r = $_ * .flip;
  1000 < $r and $_ == [+] $r.comb and say $r
}

^37 产生一个范围 0 .. ^37, 就是 0到36之前的数,在表达式中代表 B

my $b;
for 1000..^10000 -> $i {
  $b=[+] $i.comb;
  say $i if $b*$b.flip == $i;
}

输出:

1458
1729
  • 9、 大小写转换
> my $word= "I Love Raku"
I Love Raku
> $word.wordcase()
I Love Raku
> my $lowercase = "i love perl 6"
i love perl 6
> $lowercase.wordcase()
I Love Raku
> $word.samecase('A')
I LOVE PERL 6
> $word.samecase('a')
i love perl 6
> $word.samecase('a').wordcase()
I Love Raku
  • 10、 多行文本
my $string = q:to/THE END/;
Norway
    Oslo : 59.914289,10.738739 : 2
    Bergen : 60.388533,5.331856 : 4
Ukraine
    Kiev : 50.456001,30.50384 : 3
Switzerland
    Wengen : 46.608265,7.922065 : 3
THE END

say $string;
  • 11、 超运算符与子例程
my @a = <1 2 3 4>;

sub by2($n){
    return 2*$n;
}

sub power2($n) {
    return $n ** 2;
}

my @b = @a».&by2».&power2;
say @b; # 4 16 36 64

为什么是 &function 呢:

the name of the by2 function is &by2, just as the name of the foo scalar is $foo and the name of the foo array is @foo

  • 12、 如何在 Raku 中执行外部命令并捕获输出
> my $res = qqx{mkdir 123456}

# 或使用 qx{ }
> my $res = qx{mkdir 112233}
  • 13、Does Raku support something equivalent to Perl5’s DATA and END sections?
=foo This is a Pod block. A single line one. This Pod block's name is 'foo'.

=begin qux
This is another syntax for defining a Pod block.
It allows for multi line content.
This block's name is 'qux'.
=end qux

=data A data block -- a Pod block with the name 'data'.

# Data blocks are P6's version of P5's __DATA__.
# But you can have multiple data blocks:

=begin data
Another data block.
This time a multi line one.
=end data

$=pod.grep(*.name eq 'data').map(*.contents[0].contents.say);

say '-' x 45;

for @$=pod {
  if .name eq 'data' {
    say .contents[0].contents
  }
}
  • 14、生成含有26个英文字母和下划线的 junction
any('A'..'Z','a'..'z','_');
  • 15、判断一个字符是否在某个集合中
>  so any('A'..'Z','a'..'z')  set("12a34".comb)

“12a34”.comb 会把字符串分割为单个字符,返回一个字符数组。

  • 16、生成 IP 地址范围
.say for "192.168.10." «~» (0..255).list
  • 17、 生成 OC 中的测试数组
.say for "@" «~» '"Perl' «~» (1..30).list «~» '",'
@"Perl"1",
@"Perl"2",
@"Perl"3",
@"Perl"4",
@"Perl"5",
…
  • 18、我想以 AGCT 4 种字母为基础生成字符串。

比如希望长度为1,输出A,G,C,T。

如果长度为2,输出 AA,AG,AC,AT,GA,GG,GC,GT,CA,CG,CC,CT,TA,TG,TC,TT。这样的结果。

@a X~ ""             # 长度为1
(@a X~ @a)           # 长度为2
(@a X~ @a) X~ @a     # 长度为3
@a X~ @a X~ @a X~ @a # 长度为4
> my @a=<A G C T>
A G C T
> my $x=@a
A G C T
> $x xx 2
A G C T A G C T
> $x xx 3
A G C T A G C T A G C T
> ($x xx 3).WHAT
(List)
> $x.WHAT
(Array)

> ([X~] $x xx 2).join(',')
AA,AG,AC,AT,GA,GG,GC,GT,CA,CG,CC,CT,TA,TG,TC,TT

惰性操作符:

my @a=<A G C T>;
my $x=@a;  # 或者使用 $x = @('A','G','C','T')
for 1 ...^ * -> $a {(([X~] $x xx $a)).join(',').say;last if $a==4;};

Best Of Raku #

  • Command Line 命令行
 #               Perl 5                                     Raku
 print "bananas are good\n";                     say "bananas are good";
 print "and I said: $quotes{\"me\"}\n";          say "and I said: %quotes{"me"}.";
 print "and I said: $quotes{\"me\"}\n";          say "and I said: %quotes<me>.";
 print "What is ... ";                           $result = prompt "What is ... ";
 chomp($result = <>);
  • File IO
 #              Perl 5                                     Raku
 $content = do { local $/;                       $content = slurp "poetry.txt";
    open my $FH, "poetry.txt"; <$FH>
 };

chomp(@content = do {                            @content = lines "poetry.txt";
    open my $FH, "poetry.txt"; <$FH>
});
  • Automatic multithreading

Applying operations to junctions and arrays is now syntactically compact and readable. Raku will create threads where appropriate to use multiple processors, cores or hyperthreading for high level language SIMD concurrent processing.

 #              Perl 5                                     Raku
 my $sum;                                        my $sum = [+] @numbers;
 $sum += $_ for @numbers;
 for (0 .. $#factor1) {                          @product = @factor1 >>*<< @factor2;
   $product[$] = $factor1[$] * $factor2[$_];
 }

The Perl 5 code is a simplification, of course Raku “does the right thing” when the arrays have different lengths.

  • 比较

Here are junctions, then chained comparison operators.

 #            Perl 5                                     Raku
 if ($a == 3 or $a == 4 or $a == 7) {...}        if $a = 3 | 4 | 7 {...}
 if (4 < $a and $a < 12) {...}                   if 4 < $a < 12    {...}
 if (4 < $a and $a <= 12) {...}                  if $a ~~ 4^..12   {...}
 $a = defined $b ? $b : $c;                      $a = $b // $c;

The defined-OR operator eases lot of cases where Perl 5 newbies could fall into traps.

  • Case 结构
  #            Perl 5                                      Raku
                                                     given $a {
 if ($a == 2 or $a == 5) {...} }}                      when 2 | 5  {...}
 elsif ($a == 6 or $a == 7 or $a == 8 or $a == 9) {}   when 6 .. 9 {...}
 elsif ($a =~ /g/) {...}                               when 'g'    {...}
 else {...}                                            default     {...}
                                                     }

That new construct (backported to 5.10) is clear to read, very versatile and when used in combination with junctions, becomes even clearer.

  • 强大的循环

List iteration via for is now much more versatile.

 #            Perl 5                                     Raku
 for my $i (0..15) {...}                         for ^16 -> $i        {...}
 for (my $i=15; $i>1; $i-2) {...}                for 15,*-2...1 -> $i {...}
 for my $key (keys %hash) {                      for %hash.kv -> $key, $value {
   print "$key => $hash{$key}\n"; ...              say "$key => $value"; ...
 for my $i (0..$#a) {                            for zip(@a; @b; @c) -> $a, $b, $c {...}
   my $a = @a[$i];
   my $b = @b[$i];
   my $c = @c[$i]; ...
  • 子例程中的具名参数
 #            Perl 5                                     Raku
 sub routine {                                   sub routine ($a, $b, *@rest) {...}
   my $a = shift;
   my $b = shift;
   my @rest = @_;
 }
  • Objects with auto generated new and getters and setters

Simple Object creation is now as easy as it gets.

 #              Perl 5                                     Raku
 package Heart::Gold;                            class Heart::Gold {
                                                   has $.speed;
 sub new {                                         method stop { $.speed = 0 }
   bless {speed => 0 }, shift;                   }  
 }
                                                 my Heart::Gold $hg1 .= new;
 sub speed {                                     $hg1.speed = 100;
   my $self = shift;                             my $hg2 = $hg1.clone;
   my $speed = shift;
   if (defined $speed) { $self->{speed} = $speed }
   else { $self->{speed} }
 }

 sub stop {
   my $self = shift;
   $self->{speed} = 0;
 }

Raku Variable #

  • Variable Types

Raku (as Perl 5) knows 3 basic types of variables: Scalars (single values), Arrays (ordered and indexed lists of several values) and Hashes (2 column table, with ID and associated value pairs). They can be easily distinguished, because in front of their name is a special character called sigil (latin for sign). It’s the $ (similar to S) for Scalars, @ (like an a) for Arrays and a % (kv pair icon) for a Hash. They are now invariant (not changing), which means for instance, an array vaiable starts always with an @, even if you just want a slice of the content.

$scalar
@array
@array[1]              # $array[1]   in Perl 5
@array[1,2]            # @array[1,2] in Perl 5
%hash
%hash{'ba'}            # $hash{'ba'} in Perl 5
%hash{'ba','da','bim'} # @hash{'ba','da','bim'} in Perl 5

The sigils also mark distinct namespaces, meaning: in one lexical scope you can have 3 different variables named $stuff, @stuff and %stuff. These sigils can also be used as an operator to enforce a context in which the following data will be seen.

The fourth namespace is for subroutines and similar, even if you don’t usually think of them as variables. It’s sigil & is used to refer to subroutines without calling them.

All special namespaces from Perl 5 (often marked with special syntax), like tokens (PACKAGE), formats, file or dir handles, or builtins are now regular variables or routines.

Because all variables contain objects, they have methods. In fact, all operators, including square or curly bracket subscripts, are just methods of an object with a fancy name.

The primary sigil can be followed by a secondary sigil, called a twigil, which indicates a special scope for that variable.

Scalar #

This type stores one value, usually a reference to something: a value of a data type, a code object, an object or a compound of values like a pair, junction, array, hash or capture. The scalar context is now called item context, hence the scalar instruction from Perl 5 was renamed to item.

$CHAPTER = 3;              # first comment!
$bin = 0b11;               # same value in binary format
$pi = 3.14159_26535_89793; # the underscores just ease reading
$float = 6.02e-23;         # floating number in scientific notation
$text = 'Welcome all!';    # single quoted string

# double quoted string, does eval $pi to it's content
$text = " What is $pi?";
$text = q:to'EOT';         # heredoc string

    handy for multiline text
    like HTML templates or email

EOT
$handle = open $file_name; # file handle
# an object from a class with a nested namespace
$object = Class::Name.new();
$condition = 3|5|7;                # a junction, a logical conjunction of values
$arrayref = [0,1,1,2,3,5,8,13,21]; # an array stored as a single item

# a hash stored as a single item
$hashref = {'audreyt' => 'pugs',
            'pm'      => 'pct',
            'damian'  => 'larrys evil henchman'};
# pointing to a callable
$coderef = sub { do_something_completely_diffenent(@_) };

(For info on some of those terms: comment, binary format, the underscores ease reading, scientific notation, single-quoted string, double-quoted string, heredoc string, file handle, class, junction, list of values, hash, callable.)

Unlike Perl 5, references are automatically dereferenced to a fitting context. So you could use these $arrayrefs and $hashrefs similarly to an array or hash, making $ the universal variable prefix, pretty much like in PHP. The primary difference is that $ prefixed lists are not flattened in lists.

my $a = (1, 2, 3);
my @a = 1, 2, 3;
for $a { }          # just one iteration
for @a { }          # three iterations

Scalar Methods #

my $chapter = 3;
undefine $chapter;
defined $a; # false, returns 0
  • Array

An array is an ordered and indexed list of scalars. If not specified otherwise, they can be changed, expanded and shortened anytime and used as a list, stack, queue and much more. As in Haskell, lists are processed lazily, which means: the compiler looks only at the part it currently needs. This way Raku can handle infinite lists or do computation on lists that have not been computed yet. The lazy command enforces this and the eager command forces all values to be computed.

The list context is forced with a @() operator or list() command. That’s not autoflattening like in Perl 5 (automatically convert a List of Lists into one List). If you still want that, say flat(). Or say lol() to explicitly prevent autoflattening.

@primes = (2,3,5,7,11,13,17,19,23); # an array gets filled like in Perl 5
@primes =  2,3,5,7,11,13,17,19,23 ; # same thing, since unlike P5 round braces just do group
@primes = <2 3 5 7 11 13 17 19 23>; # ditto, <> is the new qw()
$primes = (2,3,5,7,11,13,17,19,23); # same array object just sits in $primes, $primes[0] is 2
$primes = item @primes;             # same thing, more explicit
$primes = 2,;                       # just 2, first element of the Parcel
@primes = 2;                        # array with one element
@primes = [2,3,5,7,11,13,17,19,23]; # array with one element (List of Lists - LoL)
@dev    = {'dan' => 'parrot'};      # array with one element (a Hash)
@data   = [1..5],[6..10],[11..15];  # Array of Arrays (LoL)
@list   = lol @data;                # no change
@list   = flat @data;               # returns 1..15
  • Array Slices
@primes                       # all values as list
@primes.values                # same thing
@primes.keys                  # list of all indices
"@primes[]"                   # insert all values in a string, uses [] to distinguish from mail adresses
$prime = @primes[0];          # get the first prime
$prime = @primes[*-1];        # get the last one
@some = @primes[2..5];        # get several
$cell = @data[1][2];          # get 8, third value of second value (list)
$cell = @data[1;2];           # same thing, shorten syntax
@numbers = @data[1];          # get a copy of the second subarray (6..10)
@copy = @data;                # shallow copy of the array
  • Array Methods

Some of the more important things you can do with lists. All the methods can also used like ops in “elems @array;”

? @array;              # boolean context, Bool::True if array has any value in it, even if it's a 0
+ @array;              # numeric context, number of elements (like in Perl 5 scalar @a)
~ @array;              # string context, you get content of all cells, stringified and joined, same as "@primes[]"

@array.elems;          # same as + @array
@array.end;            # number of the last element, equal to @array.elems-1
@array.cat;            # same ~ @array
@array.join('');       # also same result, you can put another string as parameter that gets between all values
@array.unshift;        # prepend one value to the array
@array.shift;          # remove the first value and return it
@array.push;           # add one value on the end
@array.pop;            # remove one value from the end and return it
@array.splice($pos,$n);# starting at $pos remove $n values and replace them with values that follow those two
  • parameters
@array.delete(@ind);   # delete all cells with indices in @ind
@array.exists(@ind);   # Bool::True if all indices of @ind have a value (can be 0 or '')
@array.pick([$n]);     # return $n (default is 1) randomly selected values, without duplication
@array.roll([$n]);     # return $n (default is 1) randomly selected values, duplication possible (like roll dice)
@array.reverse;        # all elements in reversed order
# returns a list where $n times first item is taken to last
# position if $n is positive, if negative the other way around
@array.rotate($n);

@array.sort($coderef); # returns a list sorted by a user-defined criteria, default is alphanumerical sorting
@array.min;            # numerical smallest value of that array
@array.max;            # numerical largest value of that array
$a,$b= @array.minmax;  # both at once, like in .sort,  .min, or .max, a sorting algorithm can be provided

@array.map($coderef);  # high oder map function, runs $coderef with every value as $_ and returns the list or results
@array.classify($cr);  # kind of map, but creates a hash, where keys are the results of $cr and values are from @array
@array.categorize($cr);# kind of classify, but closure can have no (Nil) or several results, so a key can have a list of values
@array.grep({$_>1});   # high order grep, returns only these elements that pass a condition ($cr returns something positive)
@array.first($coder);  # kind of grep, return just the first matching value
@array.zip;            # join arrays by picking first element left successively from here and then there
There is even a whole class of metaoperators that work upon lists.
  • Hash

In Raku a Hash is an unordered list of Pairs. A Pair is a single key => value association and appears in many places of the language syntax. A hash allows lookup of values by key using {} or <> syntax.

%dev =  'pugs'=>'audreyt', 'pct'=>'pm', "STD"=>'larry';
%dev = :rakudo('jnthn'), :testsuite('moritz');            # adverb (pair) syntax works as well
%dev = ('audreyt', 'pugs', 'pm', 'pct', 'larry', "STD");  # lists get autoconverted in hash context
%compiler = Parrot => {Rakudo => 'jnthn'}, SMOP => {Mildew => 'ruoso'};       # hash of hashes (HoH)
  • Hash Slices
$value = %dev{'key'};      # just give me the value related to that key, like in P5
$value = %dev<pm>;         # <> autoquotes like qw() in P5
$value = %dev<<$name>>;    # same thing, just with eval
@values = %dev{'key1', 'key2'};
@values = %dev<key1 key2>;
@values = %dev<<key1 key2 $key3>>;
%compiler<Parrot><Rakudo>; # value in a HoH, returns 'jnthn'
%compiler<SMOP>;           # returns the Pair: Mildew => 'ruoso'

%dev   {'audrey'};         # error, spaces between varname and braces (postcircumfix operator) are no longer allowed
%dev\  {'allison'};        # works, quote the space
%dev   .<dukeleto>;        # error
%dev\ .{'patrick'};        # works too, "long dot style", because it's an object in truth
  • Hash Methods
? %dev                     # bool context, true if hash has any pairs
+ %dev                     # numeric context, returns number of pairs(keys)
~ %dev                     # string context, nicely formatted 2 column table using \t and \n

$table = %dev;             # same as ~ %dev
%dev.say;                  # stringified, but only $key and $value are separated by \t
@pairs = %dev;             # list of all containing pairs
%dev.pairs                 # same thing in all context
%dev.elems                 # same as + %dev or + %dev.pairs
%dev.keys                  # returns a list of all keys
%dev.values                # list of all values
%dev.kv                    # flat list with key1, value1, key 2 ...
%dev.invert                # reverse all key => value relations
%dev.push (@pairs)         # inserts a list of pairs, if a key is already present in %dev, both values gets added to an array
  • Callable

Internally subroutines, methods and alike are variables with the sigil & and stored in a fourth namespace. Unlike Perl 5, all subroutines can be overwritten or augmented with user defined routines. Of course scalars can also contain routines.

&function = sub { ... };         # store subroutine in callable namespace
function();                      # call/run it

$coderef = sub { ... };          # store it in a scalar
$coderef($several, $parameter);  # run that code
  • Data Types

In contrast to variable types (container types) every value has a type too. These are organized internally as classes or roles and can be categorized into 3 piles: the undefined, immutable, and the mutable types.

You can assign one of these types to scalar, array, or hash variables, which enforces the contents to be that type.

my Int $a;
my Int @a;  # array of Int
  • Pair

Pairs are new and their syntax is used nearly everywhere in the language where there is an association between a name and a value.

$pair = 'jakub' => 'helena';  # "=>" is the pair constructor
$pair = :jakub('helena');     # same in adverbial notation
$pair = :jakub<helena>;       # same using <>, the new qw()
$pair.key                     # returns 'jakub'
$pair.value                   # returns 'helena'
$pair.isa(Pair)               # Bool::True
  • Capture

Captures are also a new type, which holds the parameters a routine gets. Because Perl now knows both positional and named parameters, it is a mixture of a list and array.

$cap = \(@a,$s,%h,'a'=>3);    # creating a capture, "\" was free since there are no references anymore
|$cap                         # flatten into argument list (without |, it will pass it as a single value)
||$cap                        # flatten into semicolon list (meant for variadic functions that take list of lists)

One important difference between a capture and a compound structure of lists and hashes: While assignments with = will copy the complete content of the named variables, this is not so in the case of a capture. When I change sinthelastexample, thecontentofcap changes too, because when parameters to a routine are variables, they are also interpolated in the moment the routine is called, not when it’s defined.

  • Assignment and Binding

  • Assignment

As rightfully expected, assignments are done with the equal sign. But unlike Perl 5 you always get a copy of the right side data assigned to the left, no matter how nested the data structure was (lists of lists eg). You never get in Raku a reference with =. The only exception may be seen captures.

my @original = [1,2],[3,4];
my $copy = @original[0]; # $copy points to [1,2]
@original[0][0] = 'fresh stuff'; # $copy[0] holds still 1
  • Binding

Since every variable in Raku is a reference, programmers can use binding to get 2 variables that point to the same memory location.

$original = 5;
$original := $mirror;       # normal binding, done on runtime
$original ::= $mirror;      # same thing, but done during compile time
$original = 3;
say $mirror;                # prints 3
$original =:= $mirror       # true, because they're bound together
$original === $mirror       # also true, because content and type are equal
  • FP oriented
sub balanced($s) {
    .none < 0 and .[*-1] == 0
        given [\+] '\\' «leg« $s.comb;
}

my $n = prompt "Number of bracket pairs: ";
my $s = <[ ]>.roll($n*2).join;

say "$s { balanced($s) ?? "is" !! "is not" } well-balanced"
  • String munging
sub balanced($_ is copy) {
    () while s:g/'[]'//;
    $_ eq '';
}

my $n = prompt "Number of bracket pairs: ";
my $s = <[ ]>.roll($n*2).join;

say "$s is", ' not' xx not balanced($s)), " well-balanced";
  • Parsing with a grammar
grammar BalBrack { token TOP { '[' <TOP>* ']' } }

my $n = prompt "Number of bracket pairs: ";
my $s = ('[' xx $n, ']' xx $n).pick(*).join;
say "$s { BalBrack.parse($s) ?? "is" !! "is not" } well-balanced";
  • 凯撒加密

实现一个凯撒加密, 编码和解码都要有

key 是一个 1 到 25 之间的整数

my @alpha = 'A' .. 'Z';

sub encrypt ( $key where 1..25, $plaintext ) {
    $plaintext.trans( @alpha Z=> @alpha.rotate($key) );
}

sub decrypt ( $key where 1..25, $cyphertext ) {
    $cyphertext.trans( @alpha.rotate($key) Z=> @alpha );
}

my $original = 'THE FIVE BOXING WIZARDS JUMP QUICKLY';
my $en = encrypt( 13, $original );
my $de = decrypt( 13, $en );

.say for $original, $en, $de;

say 'OK' if $original eq all( map { .&decrypt(.&encrypt($original)) }, 1..25 );
Output:
THE FIVE BOXING WIZARDS JUMP QUICKLY
GUR SVIR OBKVAT JVMNEQF WHZC DHVPXYL
THE FIVE BOXING WIZARDS JUMP QUICKLY
OK
  • 日期格式化

使用 “2007-11-10” 和 “Sunday, November 10, 2007” 日期格式显式当前日期:

use DateTime::Utils;

my $dt = DateTime.now;

say strftime('%Y-%m-%d', $dt);
say strftime('%A, %B %d, %Y', $dt);
  • 阶乘

n 的阶乘定义为 n*(n-1)*(n-2)…*1, 零的阶乘为1.

定义一个函数返回一个数字的阶乘。

  • 使用自定义后缀操作符

sub postfix:<!>($n where $n > 0) {
    [*] 2..$n
}

say 5!
  • [*]
my @a = 1, [\*] 1..*;
say @a[5];

标量容器中存储的对象不会在 flattening 上下文中插值,即使那个对象是可迭代的。

my @a = 3,4,5;
for 1, 2, @a { .say } # 5次迭代

输出:

1
2
3
4
5
my $s = @a;
for 1, 2, $s { ... } # 3次迭代

输出:

1
2
3 4 5

这里,$s@a 指向同一个数组对象,但是标量容器的出现阻止 $s 被展开到 for 循环中。

.list 和 .flat 方法能被用于还原展开行为:

for 1, 2, $s.list { .say }    # 5次遍历
for 1, 2, @($s)   { .say  }   # 5次遍历,@()会强制为列表上下文

输出:

1
2
3
4
5

相反,.item 方法和 $() 能用于防止插值:

my @b = 1,2,@a;           # @b 有5个元素
my @c = 1,2,@a.item;      # @c 有3个元素
my @c = 1,2,$(@a);        # 同上

say +@c; # 3
  • Feed operators

feed 操作符是完全懒惰的,意味着在使用者要求任何元素之前不会执行任何操作。这就是

my @a <== grep { ... } <== map { ... } <== grep { ... } <== 1, 2, 3

是完全懒惰的。

  • Grammars

文法是一种强大的工具, 用于拆解文本,并通常返回数据结构 例如, Raku 是使用 Raku 风格的文法解析和执行的. 对普通 Raku 用户来说,一个更实用的例子就是 JSON::Simple 模块, 这个模块能反序列化任何有效的 JSON 文件, 反序列化代码还写了不到 100 行, 简单,可扩展.

词法允许你组织正则, 就像类允许你组织普通代码的方法一样.

命名正则 #

命名正则有特殊的语法, 与子例程的定义类似:

my regex number { \d+ [ \. \d+ ]? }

这个例子中, 我们必须使用 my 关键词指定这个正则是词法作用域的, 因为 命名正则 通常用在 词法中. 给正则命名后有利于在其他地方复用正则:

say "32.51"    ~~ &number;
say "15 + 4.5" ~~ / <number> \s* '+' \s* <number> /

首先说下, 使用 regex/token/rule 定义了一个正则表达式后怎么去调用它。就像调用一个子例程那样, 使用 & 符号: & 后面跟正则表达式的名字, 即 &regex_name

regex 不是命名正则仅有的标识符 – 实际上, 它用的不多. 大多数时候, 用的最多的是 tokenrule 标识符. 它们都是不能回溯的, 这意味着正则引擎在匹配失败时不会备份和重试. 这通常是你想要的, 但不是对所有场合都合适:

my regex works-but-slow { .+ q }
my token fails-but-fast { .+ q }

# Tokens 不会沿原路返回, 这让它们更快地失败!
my $s = 'Tokens won\'t backtrack, which makes them fail quicker!';

say so $s ~~ &works-but-slow; # True
say so $s ~~ &fails-but-fast; # False, the entire string get taken by the .+

tokenrule 标识符的不同之处在于 rule 标识符让 Regex:sigspace 起作用了:

my token non-space-y { once upon a time }
my rule space-y      { once upon a time }
say 'onceuponatime'    ~~ &non-space-y;
say 'once upon a time' ~~ &space-y;

Action Classes #

实际上, 命名正则甚至能接受额外的参数, 它使用的语法跟子例程参数列表的语法一样.

​写一个程序打印从 1 到 100 的整数,但是对 3 的倍数打印 “Fizz”, 对 5 的倍数打印 “Buzz”, 对于即是 3 的倍数,又是 5 的倍数的打印 “FizzBuzz”.

for 1 .. 100 {
    when $_ %% (3 & 5) { say 'FizzBuzz'; }
    when $_ %% 3       { say 'Fizz';     }
    when $_ %% 5       { say 'Buzz';     }
    default            { .say;           }
}

Or abusing multi subs:

multi sub fizzbuzz(Int $ where * %% 15) { 'FizzBuzz' }
multi sub fizzbuzz(Int $ where * %% 5)  { 'Buzz'     }
multi sub fizzbuzz(Int $ where * %% 3)  { 'Fizz'     }
multi sub fizzbuzz(Int $number )        { $number    }
(1 .. 100)».&fizzbuzz.join("\n").say;

Most concisely:

say 'Fizz' x $_ %% 3 ~ 'Buzz' x $_ %% 5 || $_ for 1 .. 100;

And here’s an implementation that never checks for divisibility:

.say for
    (('' xx 2, 'Fizz') xx * Z~
    ('' xx 4, 'Buzz') xx *) Z||1 .. 100;