本页面试图提供从 Perl 到 Raku 的语法和语义变化的快速路径。任何在 Perl 中有效但在 Raku 中必须以不同方式编写的代码都应列在此处(而许多的 Raku 功能和习惯用法则不需要)。

因此,这不能被误认为是初学者教程或 Raku 的宣传概述;它旨在作为具有扎实 Perl 背景的 Raku 学习者以及将 Perl 代码移植到 Raku 的任何人的技术参考(尽管请注意,自动翻译 可能更方便)。

关于语义的说明;当我们在本文档中说“现在”时,我们通常只是指“现在你正在尝试 Raku”。我们并不意味着暗示 Perl 现在突然过时了。恰恰相反,我们大多数人都喜欢 Perl,我们预计 Perl 将在未来很多年内继续使用。事实上,我们最重要的目标之一是使 Perl 和 Raku 之间的交互顺利进行。但是,我们也喜欢 Raku 中的设计决策,这些决策当然比 Perl 中许多历史设计决策更新,并且可以说更集成。因此,我们中的许多人确实希望在未来十年或二十年内,Raku 将成为更占主导地位的语言。如果你想在未来的意义上理解“现在”,那也很好。但我们对导致争端的非此即彼的思维方式一点也不感兴趣。

请注意,一些历史文档可能将 Raku 称为其原始名称 Perl 6,或将 Perl 特指为 Perl 5。

CPAN§

参见 https://raku.land/.

如果您使用的模块尚未转换为 Raku,并且本文档中未列出任何替代方案,那么它在 Raku 下的使用可能尚未得到解决。

Inline::Perl5 项目使您可以通过使用嵌入式 perl 解释器实例来运行 Perl 代码,从而直接从 Raku 代码中 use Perl 模块。

这与以下操作一样简单

# the :from<Perl5> makes Raku load Inline::Perl5 first (if installed) 
# and then load the Scalar::Util module from Perl 
use Scalar::Util:from<Perl5> <looks_like_number>;
say looks_like_number "foo";   # OUTPUT: «0␤» 
say looks_like_number "42";    # OUTPUT: «1␤» 

作为 CPAN 蝴蝶计划的一部分,许多 Perl 模块已移植到 Raku,试图尽可能地维护这些模块的 API。这些可以在 https://raku.land/?q=CPAN5 中找到。

许多 Perl 内置函数(到目前为止约 100 个)已移植到 Raku,具有相同的语义。考虑 Perl 中的 shift 函数,它默认情况下具有从 @_@ARGV 中神奇地移位的功能,具体取决于上下文。这些可以在 https://raku.land/zef:lizmat/P5built-ins 及其依赖项中找到。

语法§

两种语言在语法上有一些区别,从标识符的定义方式开始。

标识符§

Raku 允许在标识符中使用连字符 (-)、下划线 (_)、撇号 (') 和字母数字。

sub test-doesn't-hang { ... }
my $ความสงบ = 42;
my \Δ = 72say 72 - Δ;

-> 方法调用§

如果你读过任何 Raku 代码,你会立即发现方法调用语法现在使用点而不是箭头。

$person->name  # Perl

$person.name   # Raku 

点表示法既易于键入,又是行业标准。但我们也希望将箭头用于其他用途。(如果你想知道,现在使用 ~ 运算符进行连接。)

调用在运行时才知道名称的方法

$object->$methodname(@args);  # Perl

$object."$methodname"(@args); # Raku 

如果你省略引号,那么 Raku 期望 $methodname 包含一个 Method 对象,而不是方法的简单字符串名称。是的,Raku 中的所有内容都可以被视为对象。

空白§

Perl 允许在使用空白方面有惊人的灵活性,即使在打开严格模式和警告的情况下也是如此。

# unidiomatic but valid Perl
say"Hello ".ucfirst  ($people
    [$ i]
    ->
    name)."!"if$greeted[$i]<1;

Raku 也支持程序员的自由和创造力,但将语法灵活性与它的设计目标相平衡,即拥有一个一致、确定性、可扩展的语法,支持单遍解析和有用的错误消息,干净地集成诸如自定义运算符之类的功能,并且不会导致程序员意外地错误地表达他们的意图。此外,"代码高尔夫" 的做法略微淡化了;Raku 的设计目的是在概念上比在按键上更简洁。

因此,在语法中有一些地方,空白在 Perl 中是可选的,但在 Raku 中是强制性的或禁止的。许多这些限制不太可能影响到现实生活中的 Perl 代码(例如,在变量的符号和名称之间不允许使用空白),但有一些限制不幸地会与一些 Perl 黑客的习惯编码风格发生冲突。

  • 在参数列表的左括号之前不允许使用空格。

    substr ($s, 4, 1); # Perl (in Raku this would try to pass a single
                           #         argument of type List to substr)
    
    
    substr($s41);  # Raku 
    substr $s41;   # Raku - alternative parentheses-less style 

    如果这对你来说确实是一个问题,那么你可能想看看 Raku 生态系统中的 Slang::Tuxic 模块:它以一种方式改变了 Raku 的语法,让你可以在参数列表的左括号之前使用空格。

  • 关键字后必须使用空格。

    my($alpha, $beta);          # Perl, tries to call my() sub in Raku
    
    
    my ($alpha$beta);         # Raku 
    if($a < 0) { ... }          # Perl, dies in Raku
    
    
    if ($a < 0{ ... }         # Raku 
    if $a < 0 { ... }           # Raku, more idiomatic 
    while($x-- > 5) { ... }     # Perl, dies in Raku
    
    
    while ($x-- > 5{ ... }    # Raku 
    while $x-- > 5 { ... }      # Raku, more idiomatic 
  • 在前缀运算符之后或后缀/后环绕运算符之前不允许使用空格(包括数组/哈希下标)。

    $seen {$_} ++; # Perl
    
    
    %seen{$_}++;   # Raku 
  • 如果与现有的后缀/后环绕运算符冲突,则在中缀运算符之前需要使用空格。

    $n<1;   # Perl (in Raku this would conflict with postcircumfix < >)
    
    
    $n < 1# Raku 
  • 但是,在方法调用的句点之前允许使用空格!

    # Perl
    my @books = $xml
      ->parse_file($file)          # some comment
      ->findnodes("/library/book");
    
    
    # Raku 
    my @books = $xml
      .parse-file($file)           # some comment 
      .findnodes("/library/book");

但是,请注意,你可以使用 unspace 在 Raku 代码中添加空格,在其他情况下不允许使用空格的地方。

另请参阅 语法页面中的其他词法约定

符号§

在 Perl 中,数组和哈希使用不同的符号,具体取决于它们是如何被访问的。在 Raku 中,符号是不变的,无论变量是如何被使用的 - 你可以将它们视为变量名称的一部分。

$ 标量§

$ 符号现在始终与 "标量" 变量一起使用(例如 $name),不再用于 数组索引哈希索引。也就是说,你仍然可以使用 $x[1]$x{"foo"},但它会作用于 $x,对同名 @x%x 没有影响。现在将使用 @x[1]%x{"foo"} 访问它们。

@ 数组§

@ 符号现在始终用于 "数组" 变量(例如 @months@months[2]@months[2, 4]),不再用于 值切片哈希

% 哈希§

% 符号现在始终用于 "哈希" 变量(例如 %calories%calories<apple>%calories<pear plum>),不再用于 键/值切片数组

& 子例程§

& 符号现在始终用于一致地(并且无需反斜杠的帮助)引用命名子例程/运算符的函数对象,而不调用它,即使用名称作为 "名词" 而不是 "动词"。

my $sub = \&foo; # Perl

my $sub = &foo;  # Raku 
callback => sub { say @_ }  # Perl - can't pass built-in sub directly

callback => &say            # Raku - & gives "noun" form of any sub 

由于 Raku 不允许在词法作用域完成编译后添加/删除符号,因此没有等效于 Perl 的 undef &foo;,而最接近 Perl 的 defined &foo 的等效项将是 defined ::('&foo')(它使用 "动态符号查找" 语法)。但是,您可以使用 my &foo; 声明一个可变的命名子例程,然后通过分配给 &foo 在运行时更改其含义。

在 Perl 中,与符号可以额外用于以特殊方式调用子例程,与正常的子例程调用相比,其行为略有不同。在 Raku 中,这些特殊形式不再可用。

  • &foo(...) 用于绕过函数原型

    在 Raku 中没有原型,并且您是否将文字代码块或保存代码对象的变量作为参数传递不再有任何区别。

    # Perl:
    first_index { $_ > 5 } @values;
    &first_index($coderef, @values); # (disabling the prototype that parses a
                                         # literal block as the first argument)
    
    
    # Raku: 
    first { $_ > 5 }@values:k;   # the :k makes first return an index 
    first $coderef@values:k;
  • &foo; goto &foo; 用于重新使用调用者的参数列表/在调用堆栈中替换调用者。Raku 可以使用 callsame 进行重新调度,或者使用 nextsamenextwith,它们在 Perl 中没有完全等效的项。

    sub foo { say "before"; &bar;     say "after" } # Perl
    
    
    sub foo { say "before"bar(|@_); say "after" } # Raku - have to be explicit 
    sub foo { say "before"; goto &bar } # Perl
    
    
    proto foo (|) {*};
    multi foo ( Any $n ) {
        say "Any"say $n;
    };
    multi foo ( Int $n ) {
        say "Int"callsame;
    };
    foo(3); # /language/functions#sub_callsame 

* 通配符§

在 Perl 中,* 符号指的是 Perl 用于存储非词法变量、文件句柄、子例程和格式的 GLOB 结构。

[1]

您最有可能在早期 Perl 版本编写的代码中遇到 GLOB,该版本不支持词法文件句柄,当需要将文件句柄传递给子例程时。

# Perl - ancient method
sub read_2 {
    local (*H) = @_;
    return scalar(<H>), scalar(<H>);
}
open FILE, '<', $path or die;
my ($line1, $line2) = read_2(*FILE);

您应该重构您的 Perl 代码以消除对 GLOB 的需要,然后再翻译成 Raku。

# Perl - modern use of lexical filehandles
sub read_2 {
    my ($fh) = @_;
    return scalar(<$fh>), scalar(<$fh>);
}
open my $in_file, '<', $path or die;
my ($line1, $line2) = read_2($in_file);

这里只是一种可能的 Raku 翻译。

# Raku 
sub read-n($fh$n{
    return $fh.get xx $n;
}
my $in-file = open $path or die;
my ($line1$line2= read-n($in-file2);

[] 数组索引/切片§

数组上的索引和切片操作不再影响变量的 符号,并且可以使用副词来控制切片的类型。

  • 索引

    say $months[2]; # Perl
    
    
    say @months[2]; # Raku - @ instead of $ 
  • 值切片

    say join ','@months[68..11]; # Perl and Raku 
  • 键/值切片

    say join ',', %months[6, 8..11];    # Perl
    
    
    say join ','@months[68..11]:kv# Raku - @ instead of %; use :kv adverb 

还要注意,下标方括号现在是一个正常的后缀运算符,而不是特殊的语法形式,因此 检查元素是否存在取消设置元素 是使用副词完成的。

数组的最大索引现在可以使用 .end 方法获得。

say $#item;    # Perl

say @item.end# Raku 

{} 哈希索引/切片§

哈希上的索引和切片操作不再影响变量的 符号,并且可以使用副词来控制切片的类型。此外,单字下标不再在花括号内神奇地自动引用;相反,可以使用新的尖括号版本,它始终自动引用其内容(使用与 qw// 引用构造相同的规则)。

  • 索引

    say $calories{"apple"}; # Perl
    
    
    say %calories{"apple"}# Raku - % instead of $ 
    say $calories{apple};   # Perl
    
    
    say %calories<apple>;   # Raku - angle brackets; % instead of $ 
    say %calories«"$key"»;  # Raku - double angles interpolate as a list of Str 
  • 值切片

    say join ',', @calories{'pear', 'plum'}; # Perl
    
    
    say join ','%calories{'pear''plum'}# Raku - % instead of @ 
    say join ','%calories<pear plum>;      # Raku (prettier version) 
    my $keys = 'pear plum';
    say join ','%calories«$keys»;          # Raku the split is done after interpolation 
  • 键/值切片

    say join ',', %calories{'pear', 'plum'};    # Perl
    
    
    say join ','%calories{'pear''plum'}:kv# Raku - use :kv adverb 
    say join ','%calories<pear plum>:kv;      # Raku (prettier version) 

还要注意,下标花括号现在是一个正常的后缀运算符,而不是特殊的语法形式,因此 检查键是否存在删除键 是使用副词完成的。

创建引用并使用它们§

在 Perl 中,匿名数组、哈希和子程序的引用在创建时返回。对现有命名变量和子程序的引用是使用 `\` 运算符生成的。 “引用/解引用”的隐喻不能很好地映射到实际的 Raku 容器系统,因此我们将不得不关注引用运算符的意图,而不是实际的语法。

my $aref = \@aaa  ; # Perl

这可能用于将引用传递给例程,例如。但在 Raku 中,传递的是(单个)底层对象(您可以将其视为一种按引用传递)。

my @array = 4,8,15;
{ $_[0= 66 }(@array);   # run the block with @array aliased to $_ 
say @array#  OUTPUT: «[66 8 15]␤» 

传递的是 `@array` 的底层 Array 对象,并且它的第一个值在声明的例程中被修改。

在 Perl 中,解引用整个引用的语法是类型符号和花括号,引用在花括号内。在 Raku 中,这个概念根本不适用,因为引用的隐喻实际上不适用。

在 Perl 中,箭头运算符 `->` 用于单次访问复合引用的引用或通过其引用调用子程序。在 Raku 中,点运算符 `.` 始终用于对象方法,但其余部分实际上不适用。

# Perl
    say $arrayref->[7];
    say $hashref->{'fire bad'};
    say $subref->($foo, $bar);

在相对较新的 Perl 版本(5.20 及更高版本)中,一项新功能允许使用箭头运算符进行解引用:参见 后缀解引用。这可以用来从标量创建数组。此操作通常称为decont,如解容器化,在 Raku 中,使用诸如 `list` 和 `hash` 之类的方法。

# Perl 5.20
    use experimental qw< postderef >;
    my @a = $arrayref->@*;
    my %h = $hashref->%*;
    my @slice = $arrayref->@[3..7];

# Raku 
    my @a = $contains-an-array.list;        # or @($arrayref) 
    my %h = $contains-a-hash.hash;          # or %($hashref) 

“禅”切片做同样的事情。

# Raku 
    my @a = $contains-an-array[];
    my %h = $contains-a-hash{};

有关更多信息,请参见 文档的“容器”部分

运算符§

有关所有运算符的完整详细信息,请参见 运算符文档

未更改

  • + 数值加法

  • - 数值减法

  • * 数值乘法

  • / 数值除法

  • % 数值模

  • ** 数值幂运算

  • ++ 数值增量

  • -- 数值减量

  • ! && || ^ 布尔值,高优先级

  • not and or xor 布尔值,低优先级

  • == != < > <= >= 数值比较

  • eq ne lt gt le ge 字符串比较

,(逗号)列表分隔符§

未更改,但请注意,为了将数组变量展平为列表(为了追加或添加更多项目),应该使用 `|` 运算符(另请参见 Slip)。例如

my @numbers = 100200300;
my @more_numbers = 500600700;
my @all_numbers = |@numbers400|@more_numbers;

这样就可以连接数组。

请注意,在右侧不需要任何括号:列表分隔符负责创建列表,而不是括号!

<=> cmp 三元比较§

在 Perl 中,这些运算符返回 -1、0 或 1。在 Raku 中,它们返回 `Order::Less`、`Order::Same` 或 `Order::More`。

cmp 现在命名为 `leg`;它强制字符串上下文进行比较。

<=> 仍然强制数值上下文。

Raku 中的 `cmp` 执行 `<=>` 或 `leg`,具体取决于其参数的现有类型。

~~ 智能匹配运算符§

虽然运算符没有改变,但关于究竟匹配什么的规则取决于两个参数的类型,而这些规则在 Perl 和 Raku 中并不完全相同。请参见 ~~智能匹配运算符

& | ^ 字符串按位运算§

& | ^ 数值按位运算§

& | ^ 布尔运算§

在 Perl 中,& | ^ 是根据其参数的内容调用的。例如,31 | 33 返回的结果与 "31" | "33" 不同。

在 Raku 中,这些单字符操作符已被移除,并被需要强制其参数转换为所需上下文的双字符操作符取代。

# Infix ops (two arguments; one on each side of the op)
+&  +|  +^  And Or Xor: Numeric
~&  ~|  ~^  And Or Xor: String
?&  ?|  ?^  And Or Xor: Boolean

# Prefix ops (one argument, after the op)
+^  Not: Numeric
~^  Not: String
?^  Not: Boolean (same as the ! op)

<< >> 数值左移/右移操作符§

+<+> 取代。

say 42 << 3; # Perl

say 42 +< 3# Raku 

=> 胖逗号§

在 Perl 中,=> 的作用就像逗号一样,但也会引用其左侧。

在 Raku 中,=>Pair 操作符,它在原理上完全不同,但在许多情况下工作方式相同。

如果你在哈希初始化中使用 =>,或者在传递参数给期望哈希引用的子程序时使用 =>,那么用法可能完全相同。

sub get_the_loot { ... }# Raku stub 
# Works in Perl and Raku 
my %hash = ( AAA => 1BBB => 2 );
get_the_loot'diamonds'{ quiet_level => 'very'quantity => 9 }); # Note the curly braces

如果你使用 => 作为一种方便的快捷方式来避免引用列表的一部分,或者在传递参数给期望 KEY, VALUE, KEY, VALUE 的扁平列表的子程序时使用 =>,那么继续使用 => 可能会破坏你的代码。最简单的解决方法是将该胖箭头更改为常规逗号,并手动为其左侧添加引号。或者,你可以更改子程序的 API 以 吞噬哈希。一个更好的长期解决方案是更改子程序的 API 以期望 Pair;但是,这需要你一次更改所有子程序调用。

# Perl
sub get_the_loot {
    my $loot = shift;
    my %options = @_;
    # ...
}
# Note: no curly braces in this sub call
get_the_loot( 'diamonds', quiet_level => 'very', quantity => 9 );

# Raku, original API 
sub get_the_loot$loot*%options ) { # The * means to slurp everything 
    ...
}
get_the_loot'diamonds'quiet_level => 'very'quantity => 9 ); # Note: no curly braces in this API 
 
# Raku, API changed to specify valid options 
# The colon before the sigils means to expect a named variable, 
# with the key having the same name as the variable. 
sub get_the_loot$loot:$quiet_level?:$quantity = 1 ) {
    # This version will check for unexpected arguments! 
    ...
}
get_the_loot'diamonds'quietlevel => 'very' ); # Throws error for misspelled parameter name 

? : 三元操作符§

条件操作符 ? : 已被 ?? !! 取代。

my $result = $score > 60 ?  'Pass' :  'Fail'; # Perl

my $result = $score > 60 ?? 'Pass' !! 'Fail'# Raku 

. (点) 字符串连接§

被波浪号取代。

助记符:想想用针线将两个字符串“缝合”在一起。

$food = 'grape' . 'fruit'; # Perl

$food = 'grape' ~ 'fruit'# Raku 

x 列表重复或字符串重复操作符§

在 Perl 中,x 是重复操作符,它在标量或列表上下文中表现不同

  • 在标量上下文中,x 重复字符串;

  • 在列表上下文中,x 重复列表,但前提是左侧参数必须加括号!

Raku 使用两个不同的重复操作符来实现上述功能

  • x 用于字符串重复(在任何上下文中);

  • xx 用于列表重复(在任何上下文中)。

助记符:x 很短,xx 很长,所以 xx 用于列表。

# Perl
    print '-' x 80;             # Print row of dashes
    @ones = (1) x 80;           # A list of 80 1's
    @ones = (5) x @ones;        # Set all elements to 5

# Raku 
    print '-' x 80;             # Unchanged 
    @ones = 1 xx 80;            # Parentheses no longer needed 
    @ones = 5 xx @ones;         # Parentheses no longer needed 

.. ... 两点或三点,范围操作符或翻转操作符§

在 Perl 中,.. 是两个完全不同的操作符之一,具体取决于上下文。

在列表上下文中,.. 是熟悉的范围操作符。来自 Perl 代码的范围 **不** 需要翻译。

在标量上下文中,..... 是鲜为人知的翻转操作符。它们已被 fffff 取代。

字符串插值§

在 Perl 中,"${foo}s" 将变量名与其旁边的常规文本分隔开。在 Raku 中,只需将花括号扩展以包含 sigil:"{$foo}s"。这实际上是插值表达式的非常简单的情况。

复合语句§

这些语句包括条件语句和循环语句。

条件语句§

if elsif else unless§

基本保持不变;条件周围的括号现在是可选的,但如果使用,则不能紧跟在关键字之后,否则它将被视为函数调用。将条件表达式绑定到变量的方式也略有不同

if (my $x = dostuff()) {...}  # Perl

if dostuff() -> $x {...}      # Raku 

(你仍然可以在 Raku 中使用 my 形式,但它将作用域到外部块,而不是内部块。)

unless 条件语句在 Raku 中只允许一个块;它不允许 elsifelse 子句。

given-when§

given-when 结构类似于一系列 if-elsif-else 语句,或者类似于 C 语言中的 switch-case 结构。它的一般结构如下:

given EXPR {
    when EXPR { ... }
    when EXPR { ... }
    default { ... }
}

在最简单的形式中,该结构如下所示

given $value {                   # assigns $_ 
    when "a match" {             # if $_ ~~ "a match" 
        # do-something(); 
    }
    when "another match" {       # elsif $_ ~~ "another match" 
        # do-something-else(); 
    }
    default {                    # else 
        # do-default-thing(); 
    }
}

这很简单,因为标量值在 when 语句中与 $_ 匹配,而 $_given 设置。更一般地说,匹配实际上是对 $_ 的智能匹配,因此可以使用更复杂的实体(如正则表达式)进行查找,而不是标量值。

另请参阅上面关于智能匹配运算符的警告。

循环§

while until§

基本保持不变;条件周围的括号现在是可选的,但如果使用,则不能紧跟在关键字之后,否则它将被视为函数调用。将条件表达式绑定到变量的方式也略有不同

while (my $x = dostuff()) {...}  # Perl

while dostuff() -> $x {...}      # Raku 

(你仍然可以在 Raku 中使用 my 形式,但它将作用域到外部块,而不是内部块。)

请注意,从文件句柄中逐行读取的方式已更改。

在 Perl 中,这是在使用菱形运算符的 while 循环中完成的。使用 for 而不是 while 是一个常见的错误,因为 for 会导致整个文件一次性被吸入,从而淹没程序的内存使用量。

在 Raku 中,for 语句是惰性的,因此我们使用 .lines 方法在 for 循环中逐行读取。

while (<IN_FH>)  { } # Perl

for $IN_FH.lines { } # Raku 

另请注意,在 Raku 中,默认情况下会对行进行 chomp 处理。

do while/until§

# Perl
do {
    ...
} while $x < 10;

do {
    ...
} until $x >= 10;

该结构仍然存在,但 do 已重命名为 repeat,以更好地表示该结构的作用

# Raku 
repeat {
    ...
} while $x < 10;
 
repeat {
    ...
} until $x >= 10;

请注意,Perl 中未修饰的 do 块('do {...}')在 Raku 中使用时行为相同。

for foreach§

首先要注意关于 forforeach 关键字的常见误解:许多程序员认为它们区分了 C 样式的三表达式形式和列表迭代器形式;它们没有!实际上,这些关键字是可互换的;Perl 编译器会查找括号内的分号以确定要解析哪种类型的循环。

C 样式的三因子形式现在使用 loop 关键字,并且其他方面保持不变。括号仍然是必需的。

for  ( my $i = 1; $i <= 10; $i++ ) { ... } # Perl

loop ( my $i = 1$i <= 10$i++ ) { ... } # Raku 

循环迭代器形式在 Raku 中命名为 forforeach 不再是关键字。for 循环具有以下规则

  • 括号是可选的;

  • 迭代变量(如果有)已从出现在列表之前移动到出现在列表之后以及添加的箭头运算符之后;

  • 迭代变量现在始终是词法变量:my 不需要也不允许;

  • 迭代变量是当前列表元素的只读别名(在 Perl 中它是读写别名!)。如果需要读写别名,请将迭代变量之前的 -> 更改为 <->。从 Perl 翻译时,检查循环变量的使用情况以确定是否需要读写。

for my $car (@cars)  {...} # Perl; read-write

for @cars  -> $car   {...} # Raku; read-only 
for @cars <-> $car   {...} # Raku; read-write 

在 Raku 中,与 Perl 不同,默认主题 $_ 将以相同的方式表现,在用作主题变量时变为只读。

for (@cars)      {...} # Perl; $_ is read-write

for @cars        {...} # Raku; $_ is read-only 
for @cars <-> $_ {...} # Raku; $_ is also read-write 

可以通过在箭头运算符之后指定多个变量来在每次迭代中使用列表中的多个元素

my @array = 1..10;
for @array -> $first$second {
    say "First is $first, second is $second";
}

对于迭代次数不是箭头运算符之后变量数量的倍数的情况,可以为变量提供默认值

my @array = 1..9;
for @array -> $first$second = 0 {
    say "First is $first, second is $second";
}

each§

以下是 Perl 中 while…each(%hash)while…each(@array)(即,迭代数据结构的键/索引和值)的等效项

while (my ($i, $v) = each(@array)) { ... } # Perl

for @array.kv -> $i$v { ... } # Raku 
while (my ($k, $v) = each(%hash)) { ... } # Perl

for %hash.kv -> $k$v { ... } # Raku 

流程控制语句§

未更改

  • next

  • last

  • redo

continue§

不再有 continue 块。相反,请在循环体中使用 NEXT 块(相位器)。

# Perl
    my $str = '';
    for (1..5) {
        next if $_ % 2 == 1;
        $str .= $_;
    }
    continue {
        $str .= ':'
    }

# Raku 
    my $str = '';
    for 1..5 {
        next if $_ % 2 == 1;
        $str ~= $_;
        NEXT {
            $str ~= ':'
        }
    }

请注意,相位器实际上不需要块。当您不希望另一个作用域时,这非常方便

# Raku 
    my $str = '';
    for 1..5 {
        next if $_ % 2 == 1;
        $str ~= $_;
        NEXT $str ~= ':';
    }

函数§

带有裸块的内置函数§

以前接受裸块,然后在没有逗号的情况下,紧随其后的参数的内置函数现在需要在块和参数之间使用逗号,例如 mapgrep 等。

my @results = grep { $_ eq "bars" } @foo; # Perl

my @results = grep { $_ eq "bars" }@foo# Raku 

delete§

变为 {} 哈希下标[] 数组下标 运算符的副词。

my $deleted_value = delete $hash{$key};  # Perl

my $deleted_value = %hash{$key}:delete;  # Raku - use :delete adverb 
my $deleted_value = delete $array[$i];  # Perl

my $deleted_value = @array[$i]:delete;  # Raku - use :delete adverb 

exists§

变为 {} 哈希下标[] 数组下标 运算符的副词。

say "element exists" if exists $hash{$key};  # Perl

say "element exists" if %hash{$key}:exists;  # Raku - use :exists adverb 
say "element exists" if exists $array[$i];  # Perl

say "element exists" if @array[$i]:exists;  # Raku - use :exists adverb 

正则表达式 ( regex / regexp )§

=~!~ 更改为 ~~!~~§

在 Perl 中,匹配和替换是使用 =~ 正则表达式绑定运算符对变量进行的。

在 Raku 中,使用 ~~ 智能匹配运算符代替。

next if $line  =~ /static/  ; # Perl

next if $line  ~~ /static/  ; # Raku 
next if $line  !~ /dynamic/ ; # Perl

next if $line !~~ /dynamic/ ; # Raku 
$line =~ s/abc/123/;          # Perl

$line ~~ s/abc/123/;          # Raku 

或者,可以使用新的 .match.subst 方法。请注意,.subst 是非变异的

捕获从 0 开始,而不是 1§

/(.+)/ and print $1; # Perl

/(.+)/ and print $0# Raku 

移动修饰符§

将任何修饰符从正则表达式的末尾移动到开头。这可能需要您在简单的匹配(如 /abc/)中添加可选的 m

next if $line =~    /static/i ; # Perl

next if $line ~~ m:i/static/  ; # Raku 

添加 :P5 或 :Perl5 副词§

如果实际的正则表达式很复杂,您可能希望按原样使用它(有一些例外),方法是添加 P5 修饰符。

next if $line =~    m/[aeiou]/   ; # Perl

next if $line ~~ m:P5/[aeiou]/   ; # Raku, using P5 modifier 
next if $line ~~ m/  <[aeiou]> / ; # Raku, native new syntax 

如果 Perl 正则表达式有任何修饰符,请将它们从末尾移动并放置在 P5 修饰符之后。每个修饰符都必须用冒号与任何其他修饰符隔开。例如

my $a = "abcabc";
my $b = $a;
$a =~ s/abcaBc//gi;  # Perl 5
$a ~~ s:P5:g:i/ab//; # Raku, using P5 modifier
$b ~~ s:gi/ab//;     # Raku, native new syntax
say $a;
say $b;

使用 P5 语法还需要进行的另一个调整是在表达式中指定 Unicode 时,用方括号替换花括号

next if $line =~ m/\x{2043};
next if $line ~~ m:P5/\x[2043]/;
next if $line ~~ /\x[2043]/;

请注意,Perl 正则表达式语法来自多年前,可能缺少自 Raku 项目开始以来添加的功能。Raku 在 P5 语法中未实现的两个此类功能是 Perl Unicode 属性匹配器 \p{}\P{}

特殊匹配器通常属于 <> 语法§

Perl 正则表达式支持许多特殊匹配语法的情况。它们不会在这里全部列出,但通常,断言不会被 () 包围,而是被 <> 包围。

对于字符类,这意味着

  • [abc] 变成 <[abc]>

  • [^abc] 变成 <-[abc]>

  • [a-zA-Z] 变成 <[a..zA..Z]>

  • [[:upper:]] 变成 <:Upper>

  • [abc[:upper:]] 变成 <[abc]+:Upper>

对于环视断言

  • (?=[abc]) 变成 <?[abc]>

  • (?=ar?bitrary* pattern) 变成 <before ar?bitrary* pattern>

  • (?!=[abc]) 变成 <![abc]>

  • (?!=ar?bitrary* pattern) 变成 <!before ar?bitrary* pattern>

  • (?<=ar?bitrary* pattern) 变成 <after ar?bitrary* pattern>

  • (?<!ar?bitrary* pattern) 变成 <!after ar?bitrary* pattern>

有关更多信息,请参阅 环视断言

(与 语法无关,“环视” /foo\Kbar/ 变成 /foo <( bar )> /

  • (?(?{condition))yes-pattern|no-pattern) 变成 [ <?{condition}> yes-pattern | no-pattern ]

最长令牌匹配 (LTM) 替代了交替§

在 Raku 正则表达式中,| 执行 LTM,它根据一组规则来决定哪个交替赢得了模棱两可的匹配,而不是哪个先写。

处理此问题的最简单方法是将 Perl 正则表达式中的任何 | 更改为 ||

但是,如果使用 || 编写的正则表达式被继承或组合到通过设计或错误使用 | 的语法中,则结果可能无法按预期工作。因此,当匹配过程变得复杂时,您最终需要对两者有所了解,尤其是 LTM 策略的工作原理。此外,| 可能是语法重用的更好选择。

命名捕获§

这些工作方式略有不同;此外,它们只在最新版本的 Perl 中有效。

use v5.22;
"þor is mighty" =~ /is (?<iswhat>\w+)/n;
say $+{iswhat};

在非捕获组内的 iswhat 用于实际捕获后面的内容,直到组的末尾())。捕获将进入 %+ 哈希,键为捕获的名称。在 Raku 中,命名捕获以这种方式工作

"þor is mighty" ~~ /is \s+ $<iswhat>=(\w+)/;
say $<iswhat>;

在正则表达式中进行实际的赋值;这与在正则表达式之外使用的变量语法相同。

注释§

与 Perl 一样,注释在正则表达式中按预期工作。

/ word #`(match lexical "word") /

BEGIN、UNITCHECK、CHECK、INIT 和 END§

除了 UNITCHECK 之外,所有这些特殊块在 Raku 中也存在。在 Raku 中,这些被称为 Phasers。但有一些区别!

UNITCHECK 变为 CHECK§

目前在 Raku 中没有 CHECK 块的直接等效项。Raku 中的 CHECK Phaser 与 Perl 中的 UNITCHECK 块具有相同的语义:它在发生它的编译单元完成解析时运行。这被认为比 Perl 中 CHECK 块的当前语义更合理。但出于兼容性原因,不可能更改 Perl 中 CHECK 块的语义,因此在 5.10 中引入了 UNITCHECK 块。因此,决定 Raku CHECK Phaser 将遵循更合理的 Perl UNITCHECK 语义。

不需要块§

在 Perl 中,这些特殊块必须使用花括号,这意味着一个单独的作用域。在 Raku 中,这不是必需的,允许这些特殊块与其周围的词法作用域共享作用域。

my $foo;             # Perl
BEGIN { $foo = 42 }

BEGIN my $foo = 42;  # Raku 

关于预编译的语义更改§

如果您在正在预编译的模块中放置 BEGINCHECK Phaser,那么这些 Phaser 将在预编译期间执行,而不会在加载预编译模块时执行。因此,在将模块代码从 Perl 移植到 Raku 时,您可能需要将 BEGINCHECK 块更改为 INIT 块,以确保它们在加载该模块时运行。

Pragmas§

strict§

严格模式现在默认启用。

warnings§

警告现在默认启用。

no warnings 可以通过将代码包装在 quietly {} 块中来实现。

autodie§

autodie 更改为在错误时抛出异常的函数现在通常默认返回 Failure。您可以毫无问题地测试 Failure 的定义性/真值性。如果您以任何其他方式使用 Failure,那么 Exception 将被 Failure 封装并抛出。

# Perl
open my $i_fh, '<', $input_path;  # Fails silently on error
use autodie;
open my $o_fh, '>', $output_path; # Throws exception on error

# Raku 
my $i_fh = open $input_path,  :r# Returns Failure on error 
my $o_fh = open $output_path:w# Returns Failure on error 

因为您可以毫无问题地检查真值性,所以您可以在 if 语句中使用 open 的结果

# Raku 
if open($input_path,:r-> $handle {
    .say for $handle.lines;
}
else {
    # gracefully handle the fact that the open() failed 
}

baseparent§

use baseuse parent 在 Raku 中都被 is 关键字在类声明中取代。

# Perl
package Cat;
use base qw(Animal);

# Raku 
class Cat is Animal {}

请注意,Animal 类必须在编译时已知,才能从它继承。

bigint bignum bigrat§

不再相关。

Int 现在是任意精度,Rat 的分子也是(分母限制为 2**64,之后它将自动升级到 Num 以保持性能)。如果您想要一个具有任意精度分母的 Rat,可以使用 FatRat

constant§

在 Raku 中,constant 是变量的声明符,就像 my 一样,只是变量永久锁定到其初始化表达式的结果(在编译时计算)。

因此,将 => 更改为 =

use constant DEBUG => 0; # Perl

constant DEBUG = 0;      # Raku 
use constant pi => 4 * atan2(1, 1); # Perl

taupiei# built-in constants in Raku 
τπ, 𝑒        # and their unicode equivalents 

encoding§

允许您以非 ASCII 或非 UTF8 编写脚本。Raku 目前仅对脚本使用 UTF8。

integer§

Perl 编译指示,用于使用整数算术而不是浮点算术。Raku 中没有这样的东西。如果您在计算中使用本机整数,那么这将是最接近的东西。

#Raku 
my int $foo = 42;
my int $bar = 666;
say $foo * $bar;    # uses native integer multiplication 

lib§

在编译时操作模块的查找位置。底层逻辑与 Perl **非常** 不同,但在您使用等效语法的情况下,Raku 中的 use lib 与 Perl 中的工作方式相同。

mro§

不再相关。

在 Raku 中,方法调用现在始终使用 C3 方法解析顺序。如果您需要找出给定类的父类,您可以这样调用 mro 元方法

say Animal.^mro;    # .^ indicates calling a metamethod on the object 

utf8§

不再相关:在 Raku 中,源代码应为 UTF8 编码。

vars§

在 Perl 中不鼓励使用。请参阅 https://perldoc.perl5.cn/vars.html

您应该重构您的 Perl 代码以消除对 use vars 的需求,然后再将其转换为 Raku。

命令行标志§

请参阅 Rakudo 使用的命令行标志

未更改

-c -e -h -I -n -p -v -V

-a§

更改您的代码以手动使用 .split

-F§

更改您的代码以手动使用 .split

-l§

这现在是默认行为。

-M -m§

仅保留 -M。而且,由于您不再可以使用“no Module”语法,因此使用 --M 来“no”模块不再可用。

-E§

由于所有功能都已启用,只需使用小写 -e

-d, -dt, -d:foo, -D, etc.§

替换为 ++BUG 元语法选项。

-s§

现在,开关解析由MAIN子例程的参数列表完成。

# Perl
    #!/usr/bin/perl -s
    if ($xyz) { print "$xyz\n" }
./example.pl -xyz=5
5

# Raku 
    sub MAINInt :$xyz ) {
        say $xyz if $xyz.defined;
    }
raku example.raku --xyz=5
5
raku example.raku -xyz=5
5

  • -t

已移除。

  • -P -u -U -W -X

已移除。请参阅 已移除的语法特性

  • -w

这现在是默认行为。

  • -S, -T

此功能已被移除。在 Reddit 上讨论了多种 复制“污染”模式的方法

文件相关操作§

将文本文件中的行读取到数组中§

在 Perl 中,读取文本文件行的常见习惯用法如下所示

open my $fh, "<", "file" or die "$!";
my @lines = <$fh>;                # lines are NOT chomped
close $fh;

在 Raku 中,这已简化为

my @lines = "file".IO.lines;  # auto-chomped

不要尝试将文件全部读取并按换行符分割结果字符串,因为这将得到一个比预期多一个元素的数组(也更复杂),例如

# initialize the file to read 
spurt "test-file"q:to/END/; 
first line
second line
third line
END
# read the file 
my @lines = "test-file".IO.slurp.split(/\n/);
say @lines.elems;    #-> 4

如果出于某种原因,您确实想先读取整个文件,那么您可以对slurp的结果调用lines方法

my @lines = "test-file".IO.slurp.lines;  # also auto-chomps

此外,请注意,$! 在 Raku 中与文件 IO 操作失败无关。失败的 IO 操作将返回一个 Failure,而不是抛出异常。如果您想返回失败消息,它在失败本身中,而不是在 $! 中。要执行类似于 Perl 中的 IO 错误检查和报告

my $fh = open('./bad/path/to/file':wor die $fh;

注意:使用 $fh 而不是 $!。或者,您可以将 $_ 设置为失败并使用 $_ 退出

my $fh = open('./bad/path/to/file':worelse .die# Raku

任何尝试使用失败的操作都将导致程序故障并终止。即使只是调用 .self 方法也足够了。

my $fh = open('./bad/path/to/file':w).self;

捕获可执行文件的标准输出。§

而在 Perl 中,您会这样做

my $arg = 'Hello';
my $captured = `echo \Q$arg\E`;
my $captured = qx(echo \Q$arg\E);

或者使用 String::ShellQuote(因为 \Q…\E 不完全正确)

my $arg = shell_quote 'Hello';
my $captured = `echo $arg`;
my $captured = qx(echo $arg);

在 Raku 中,您可能希望在不使用 shell 的情况下运行命令

my $arg = 'Hello';
my $captured = run('echo'$arg:out).out.slurp;
my $captured = runecho "$arg"», :out).out.slurp;

如果您确实想使用 shell,也可以这样做

my $arg = 'Hello';
my $captured = shell("echo $arg":out).out.slurp;
my $captured = qqx{echo $arg};

但请注意,在这种情况下,没有任何保护run 不使用 shell,因此无需转义参数(参数直接传递)。如果您使用 shellqqx,那么所有内容最终都会变成一个长字符串,然后传递给 shell。除非您非常仔细地验证参数,否则使用此类代码很可能会引入 shell 注入漏洞。

环境变量§

Perl 模块库路径§

Perl 使用 PERLLIBPERL5LIB 来指定模块的额外搜索路径,但 Raku 忽略了它们。相反,您需要使用 RAKULIB。目录分隔符也从 ':' 变成了 ','。

$ export PERL5LIB=/module/dir1:/module/dir2;

$ export RAKULIB=/module/dir1,/module/dir2;

与 Perl 一样,如果您没有指定 RAKULIB,则需要通过 use lib pragma 在程序中指定库路径

use lib '/some/module/lib'

请注意,RAKULIB 在 Raku 中更像是一种开发人员便利(与 Perl 中 PERL5LIB 的等效用法相反),不应被模块使用者使用,因为它将来可能会被移除。这是因为 Raku 的模块加载与操作系统路径不直接兼容。

其他§

'0' 为真§

与 Perl 不同,仅包含零 ('0') 的字符串为 True。由于 Raku 在核心中有类型,因此更有意义。这也意味着常见的模式

... if defined $x and length $x; # or just length() in modern perls

在 Raku 中变得很简单

... if $x;

dump§

消失了。

Raku 的设计允许自动且透明地保存和加载编译后的字节码。

目前 Rakudo 仅支持模块。

AUTOLOAD§

FALLBACK 方法 提供类似的功能。

从模块导入特定函数§

在 Perl 中,可以像这样从给定模块中选择性地导入函数

use ModuleName qw{foo bar baz};

在 Raku 中,使用 is export 角色指定要导出的函数;所有具有此角色的子程序都将被导出。因此,以下模块 Bar 导出子程序 foobar,但不导出 baz

unit module Bar;
 
sub foo($ais export { say "foo $a" }
sub bar($bis export { say "bar $b" }
sub baz($z{ say "baz $z" }

要使用此模块,只需 use Bar,函数 foobar 将可用

use Bar;
foo(1);    #=> "foo 1" 
bar(2);    #=> "bar 2" 

如果尝试使用 baz,将在编译时引发“未声明的例程”错误。

那么,如何重现 Perl 中能够选择性地导入函数的行为呢?通过在模块中定义一个 EXPORT 子程序,该子程序指定要导出的函数,并删除 module Bar 语句。

以前的模块 Bar 现在只是一个名为 Bar.rakumod 的文件,其内容如下

sub EXPORT(*@import-list{
    my %exportable-subs =
        '&foo' => &foo,
        '&bar' => &bar,
        ;
    my %subs-to-export;
    for @import-list -> $import {
        if grep $sub-name%exportable-subs.keys {
            %subs-to-export{$sub-name} = %exportable-subs{$sub-name};
        }
    }
    return %subs-to-export;
}
 
sub foo($a$b$c{ say "foo, $a$b$c" }
sub bar($a{ say "bar, $a" }
sub baz($z{ say "baz, $z" }

请注意,子程序不再通过 is export 角色显式导出,而是通过一个 EXPORT 子程序,该子程序指定我们要使模块中可用的子程序,然后我们填充一个包含将实际导出的子程序的哈希。@import-list 由调用代码中的 use 语句设置,因此允许我们选择性地导入模块提供的子程序。

因此,要仅导入 foo 例程,我们在调用代码中执行以下操作

use Bar <foo>;
foo(1);       #=> "foo 1" 

在这里我们看到,即使 bar 是可导出的,如果我们没有显式导入它,它就不能使用。因此,这会导致编译时出现“未声明的例程”错误

use Bar <foo>;
foo(1);
bar(5);       #!> "Undeclared routine: bar used at line 3" 

但是,这将起作用

use Bar <foo bar>;
foo(1);       #=> "foo 1" 
bar(5);       #=> "bar 5" 

还要注意,即使在 use 语句中指定,baz 仍然无法导入

use Bar <foo bar baz>;
baz(3);       #!> "Undeclared routine: baz used at line 2" 

为了使它工作,显然必须经过很多步骤。在标准用例中,通过 is export 角色指定要导出的函数,Raku 会自动以正确的方式为您创建 EXPORT 子程序,因此应该仔细考虑是否值得编写自己的 EXPORT 例程。

从模块导入特定函数组§

如果要从模块导出函数组,只需为组命名,其余部分将自动完成。当你在子程序声明中指定 is export 时,实际上是在将此子程序添加到 :DEFAULT 导出组中。但你可以将子程序添加到另一个组,或添加到多个组

unit module Bar;
sub foo() is export { }                   # added by default to :DEFAULT 
sub bar() is export(:FNORBL{ }          # added to the FNORBL export group 
sub baz() is export(:DEFAULT:FNORBL{ }  # added to both 

所以现在你可以像这样使用 Bar 模块

use Bar;                     # imports foo / baz 
use Bar :FNORBL;             # imports bar / baz 
use Bar :ALL;                # imports foo / bar / baz 

请注意,:ALL 是一个自动生成的组,它包含具有 is export 特性的所有子程序。

核心模块§

Data::Dumper§

在 Perl 中,Data::Dumper 模块用于序列化,以及由程序员对程序数据结构进行调试视图。

在 Raku 中,这些任务通过 .raku 方法完成,每个对象都有此方法。

# Given:
    my @array_of_hashes = (
        { NAME => 'apple',   type => 'fruit' },
        { NAME => 'cabbage', type => 'no, please no' },
    );
# Perl
    use Data::Dumper;
    $Data::Dumper::Useqq = 1;
    print Dumper \@array_of_hashes; # Note the backslash.

# Raku 
say @array_of_hashes.raku# .raku on the array, not on its reference. 

在 Perl 中,Data::Dumper 具有更复杂的可选调用约定,允许命名 VAR。

在 Raku 中,在变量的 sigil 前面放置一个冒号会将其变成一个 Pair,其键为变量名,值为变量值。

# Given:
    my ( $foo, $bar ) = ( 42, 44 );
    my @baz = ( 16, 32, 64, 'Hike!' );
# Perl
    use Data::Dumper;
    print Data::Dumper->Dump(
        [     $foo, $bar, \@baz   ],
        [ qw(  foo   bar   *baz ) ],
    );
# Output
#    $foo = 42;
#    $bar = 44;
#    @baz = (
#             16,
#             32,
#             64,
#             'Hike!'
#           );

# Raku 
say [ :$foo:$bar:@baz ].raku;
# OUTPUT: «["foo" => 42, "bar" => 44, "baz" => [16, 32, 64, "Hike!"]]␤» 

还有一种针对开发人员的 Rakudo 特定调试辅助工具,称为 dd(Tiny Data Dumper,非常小,以至于丢失了“t”)。这将打印 .raku 表示以及一些可以内省的额外信息,这些信息来自给定变量在 STDERR 上

# Raku 
dd $foo$bar@baz;
# OUTPUT: «Int $foo = 42␤Int $bar = 44␤Array @baz = [16, 32, 64, "Hike!"]␤» 

Getopt::Long§

现在,开关解析由MAIN子例程的参数列表完成。

# Perl
    use 5.010;
    use Getopt::Long;
    GetOptions(
        'length=i' => \( my $length = 24       ), # numeric
        'file=s'   => \( my $data = 'file.dat' ), # string
        'verbose'  => \( my $verbose           ), # flag
    ) or die;
    say $length;
    say $data;
    say 'Verbosity ', ($verbose ? 'on' : 'off') if defined $verbose;
perl example.pl
    24
    file.dat
perl example.pl --file=foo --length=42 --verbose
    42
    foo
    Verbosity on

perl example.pl --length=abc
    Value "abc" invalid for option length (number expected)
    Died at c.pl line 3.

# Raku 
    sub MAINInt :$length = 24:file($data= 'file.dat'Bool :$verbose ) {
        say $length if $length.defined;
        say $data   if $data.defined;
        say 'Verbosity ', ($verbose ?? 'on' !! 'off');
    }
raku example.raku
    24
    file.dat
    Verbosity off
raku example.raku --file=foo --length=42 --verbose
    42
    foo
    Verbosity on
raku example.raku --length=abc
    Usage:
      c.raku [--length=<Int>] [--file=<Any>] [--verbose]

请注意,Raku 在命令行解析错误时会自动生成完整的用法信息。

自动翻译§

快速找到 Perl 结构的 Raku 版本的一种方法是通过自动翻译器运行它。

注意: 这些翻译器目前都尚未完成。

蓝虎§

该项目致力于自动现代化 Perl 代码。它(目前)没有 Web 前端,因此必须在本地安装才能使用。它还包含一个单独的程序来将 Perl 正则表达式翻译成 Raku。

https://github.com/Util/Blue_Tiger/

Perlito§

在线翻译器!

该项目是一套 Perl 交叉编译器,包括 Perl 到 Raku 的翻译。它有 Web 前端,因此无需安装即可使用。它目前只支持 Perl 语法的子集。

https://fglock.github.io/Perlito/perlito/perlito5.html

Perl-ToPerl6§

已故的 Jeff Goff 的 Perl::ToPerl6 Perl 模块是围绕 Perl::Critic 的框架设计的。它旨在将 Perl 转换为可编译(即使不一定运行)的 Raku 代码,并进行最少的更改。代码转换器是可配置和可插拔的,因此您可以创建和贡献自己的转换器,并根据自己的需要自定义现有的转换器。您可以从 CPAN 安装最新版本,或在 GitHub 上跟踪项目的实时情况。在线转换器可能会在某个时间点提供。

其他翻译知识来源§

1 [↑] 这不能与 Perl 内置的 glob() 函数混淆,该函数从目录中读取文件名。