Wait the light to fall

Perl 5 到 Raku 指南 - 简而言之

焉知非鱼

这个页面试图提供从 Perl 5 到 Raku 的语法和语义变化的快速路径。无论在 Perl 5 中有什么用,必须在 Raku 中以不同的方式编写,这里应该列出(而许多新的 Raku 特性和惯用法)不需要)。

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

关于语义的注释;当我们在本文档中说“现在”时,我们大多只是说“现在你正在试用 Raku”。我们并不是要暗示 Perl 5 现在突然过时了。恰恰相反,我们大多数人都喜欢 Perl 5,我们期望 Perl 5 能够继续使用多年。实际上,我们更重要的目标之一是使 Perl 5 和 Raku 之间的交互顺利进行。然而,我们也喜欢 Raku 中的设计决策,它们比 Perl 5 中的许多历史设计决策更新,可以说是更好的集成。我们很多人都希望在接下来的十年或两年内,Raku 将成为更主要的语言。如果你想在未来的意义上采取“现在”,那也没关系。但是我们根本不会对导致战斗的任何/或者思考感兴趣。

CPAN #

参考 https://modules.raku.org/

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

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

这很简单:

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

许多 Perl 5 模块已经移植到 Raku,试图尽可能多地维护这些模块的 API,作为 CPAN Butterfly Plan 的一部分。 这些可以在 https://modules.raku.org/t/CPAN5 找到。

许多 Perl 5 内置函数(目前大约 100个)已经以相同的语义移植到 Raku。 考虑一下 Perl 5 中的 shift 函数默认情况下从 @_@ARGV 转移,具体取决于上下文。 这些可以在 https://modules.raku.org/t/Perl5 找到,作为可单独加载的模块,在 P5built-ins 包中可以一次性获取所有这些模块。

语法 #

两种语言之间的语法有一些差异,从如何定义标识符开始。

标识符 #

Raku 允许在标识符中使用破折号( - ),下划线(_),撇号(')和字母数字:

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

-> 方法调用 #

如果您已经阅读过任何 Raku 代码,那么很明显,方法调用语法现在使用的是点而不是箭头:

$person->name  # Perl 5 
$person.name   # Raku 

点符号更容易键入,更符合行业标准。 但我们也想偷取其他东西的箭头。 (如果你想知道的话,现在用 ~ 运算符完成连接。)

要调用在运行时之前名称未知的方法:

$object->$methodname(@args);  # Perl 5 
$object."$methodname"(@args); # Raku

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

空白 #

即使启用了严格的模式和警告,Perl 5也允许在使用空格时具有惊人的灵活性:

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

Raku 还支持程序员的自由和创造力,但平衡语法灵活性与其设计目标一致,即具有支持单遍解析和有用错误消息的一致,确定性,可扩展语法,干净地集成自定义运算符等功能,并且不会引导程序员 意外地错误表达他们的意图。 此外,“代码高尔夫”的实践略微不再强调; Raku 的设计理念比按键更简洁。

因此,语法中有许多地方,其中空格在 Perl 5 中是可选的,但在 Raku 中是强制的或禁止的。许多这些限制不太可能涉及很多真实的 Perl 代码(例如,不允许在空白之间使用空格) sigil和变量的名称),但有一些不幸与 Perl 黑客的习惯编码风格冲突:

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

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

如果这真的是一个问题,那么你可能想看看 Raku 生态系统中的 Slang::Tuxic 模块:它改变了 Raku 的语法,你可以在开放之前有一个空格 参数列表的括号。

  • 关键字后立即需要空格
my($alpha, $beta);          # Perl 5, tries to call my() sub in Raku 
my ($alpha, $beta);         # Raku 
if($a < 0) { ... }          # Perl 5, dies in Raku 
if ($a < 0) { ... }         # Raku 
if $a < 0 { ... }           # Raku, more idiomatic 
while($x-- > 5) { ... }     # Perl 5, dies in Raku 
while ($x-- > 5) { ... }    # Raku 
while $x-- > 5 { ... }      # Raku, more idiomatic 
  • 前缀运算符之后或 postfix/postcircumfix运算符(包括数组/散列下标)之前不允许有空格。
$seen {$_} ++; # Perl 5 
%seen{$_}++;   # Raku 
  • 中缀运算符之前所需的空白,如果它与现有的 postfix/postcircumfix运算符冲突。
$n<1;   # Perl 5 (in Raku this would conflict with postcircumfix < >) 
$n < 1; # Raku 
  • 但是,在方法调用期间允许使用空格!
# Perl 5 
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 5 中,数组和哈希值根据访问方式使用更改的符号。在 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>),而不再用于键/值切片数组

Sub #

& 符号现在一直使用(并且没有反斜杠的帮助)来引用命名子例程/运算符的函数对象而不调用它,即使用名称作为“名词”而不是“动词”:

my $sub = \&foo; # Perl 5 
my $sub = &foo;  # Raku 
callback => sub { say @_ }  # Perl 5 - can't pass built-in sub directly 
callback => &say            # Raku - & gives "noun" form of any sub 

由于 Raku 在完成编译后不允许在词法范围内添加/删除符号,因此没有等效于 Perl 5 的 undef&foo;,并且将定义与 Perl 5 定义的 &foo 最接近的符号::(’&foo’) (使用“动态符号查找”语法)。 但是,您可以使用我的&foo声明一个可变的命名子例程; 然后通过分配给&foo在运行时更改其含义。

在 Perl 5 中,与普通子调用相比,&符号可以另外用于以特殊方式调用子例程,具有略微不同的行为。 在 Raku 中,这些特殊形式不再可用:

  • &foo(…) 用于规避函数原型

在 Raku 中没有原型,不管你是否传递一个文字代码块或一个包含代码对象的变量作为参数,它就不再有区别了:

# Perl 5: 
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可以使用callame进行重新调度,也可以使用nextsame和nextx,它们在Perl 5中没有完全等效。

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

* Glob #

在Perl 5中,* sigil引用了 Perl 用于存储非词法变量,文件句柄,子和格式的 GLOB 结构。

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

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

在转换为 Raku 之前,您应该重构 Perl 5 代码以消除对 GLOB 的需求。

# Perl 5 - 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-file, 2);

数组索引/切片 #

数组上的索引和切片操作不再会影响变量的符号,副词可用于控制切片的类型:

  • 索引
say $months[2]; # Perl 5 
say @months[2]; # Raku - @ instead of $ 
  • 值切片
say join ',', @months[6, 8..11]; # Perl 5 and Raku 
  • 键/值切片
say join ',', %months[6, 8..11];    # Perl 5 
say join ',', @months[6, 8..11]:kv; # Raku - @ instead of %; use :kv adverb 

另请注意,下标方括号现在是一个普通的postcircumfix运算符,而不是一个特殊的句法形式,因此检查元素的存在未设置元素是通过副词完成的。

{} 散列索引/切片 #

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

  • 索引
say $calories{"apple"}; # Perl 5 
say %calories{"apple"}; # Raku - % instead of $ 
say $calories{apple};   # Perl 5 
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 5 
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 5 
say join ',', %calories{'pear', 'plum'}:kv; # Raku - use :kv adverb 
say join ',', %calories<pear plum>:kv;      # Raku (prettier version) 

还要注意,下标花括号现在是一个普通的 postcircumfix 操作符而不是一个特殊的语法形式,因此检查键的存在和删除键是用副词完成的。

创建引用并使用它们 #

在 Perl 5 中,在创建时返回对匿名数组和散列和 subs 的引用。 使用\运算符生成对现有命名变量和 subs 的引用。 “引用/解除引用”这个比喻并没有干净地映射到实际的 Raku 容器系统,所以我们必须关注引用运算符的意图而不是实际的语法。

my $aref = \@aaa  ; # Perl 5 

例如,这可能用于将引用传递给例程。但是在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 5 中,取消引用整个引用的语法是 type-sigil 和花括号,在花括号内引用。 在 Raku 中,这个概念根本不适用,因为参考隐喻并不真正适用。

在 Perl 5 中,箭头运算符 -> 用于单个访问复合引用或通过引用调用 sub。 在 Raku 中,点运算符。 始终用于对象方法,但其余方法并不真正适用。

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

在相对较新版本的 Perl 5(5.20及更高版本)中,新功能允许使用箭头运算符进行解除引用:请参阅 Postfix Dereferencing。 这可以用于从标量创建数组。 此操作通常称为 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) 

“Zen” 切片做同样的事情:

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

有关详细信息,请参阅文档的“容器”部分

运算符 #

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

没发生变化的:

  • + 数字加法
  • - 数字减法
  • * 数字乘法
  • / 数字除法
  • % 数字求模
  • ** 数字指数
  • ++ 数字递增
  • -- 数字递减
  • ! && || ^ 布尔,高优先级
  • not and or xor 布尔,低优先级
  • == != < > <= >= 数字比较
  • eq ne lt gt le ge 字符串比较

, (逗号) 列表分割符 #

没有改变,但请注意,为了将数组变量展平为列表(为了追加或添加更多项目的前缀),应该使用|操作员(另见Slip)。例如:

my @numbers = 100, 200, 300;
my @more_numbers = 500, 600, 700;
my @all_numbers = |@numbers, 400, |@more_numbers;

这样就可以连接数组。

请注意,右侧不需要任何括号:List Separator 负责创建列表,而不是括号!

<=> cmp 三路比较 #

在 Perl 5 中,这些运算符返回 -1, 0 或 1。在 Raku 中,它们返回 Order::LessOrder::SameOrder::More

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

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

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

~~ 智能匹配运算符 #

虽然运算符没有改变,但确切匹配的规则取决于两个参数的类型,并且这些规则在 Perl 5 和 Raku 中大不相同。请参阅 ~~smartmatch 运算符

& | ^ 字符串位运算符 #

& | ^ 数字位运算符 #

& | ^ 布尔运算符 #

在 Perl 5 中,& | ^ 根据参数的内容调用。例如,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 5 
say 42 +< 3; # Raku 

=> 胖逗号 #

在 Perl 5 中,=> 的行为就像一个逗号,但也引用了它的左侧。

在 Raku 中,=>Pair 运算符,原理上完全不同,但在许多情况下都是相同的。

如果您在哈希初始化中使用 =>,或者将参数传递给期望 hashref 的 sub,则用法可能相同。

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

如果你使用 => 作为一个方便的快捷方式,不必引用列表的一部分,或者将参数传递给一个需要 KEYVALUEKEYVALUE 的平面列表的子,那么继续使用 => 可能会破坏你的代码。 最简单的解决方法是将该胖箭头更改为常规逗号,并手动将引号添加到其左侧。 或者,您可以更改 sub 的API以slurp哈希。 一个更好的长期解决方案是将sub的API改为期望Pairs; 但是,这需要您一次更改所有 sub 调用。

# Perl 5 
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 5 
my $result = $score > 60 ?? 'Pass' !! 'Fail'; # Raku 

.(点号) 字符串连接 #

替换为波浪号。

助记:想到用针和线将两个字符串“拼接”在一起。

$food = 'grape' . 'fruit'; # Perl 5 
$food = 'grape' ~ 'fruit'; # Raku 

x 列表复制或字符串复制运算符 #

在 Perl 5 中,x 是复制运算符,它在标量或列表上下文中的行为有所不同:

  • 在标量上下文中,x 重复一个字符串;
  • 在列表上下文中 x 重复一个列表,但前提是左参数是括号!

Raku 使用两个不同的复制运算符来实现上述目的:

  • x 表示字符串重复(在任何上下文中);
  • xx 表示列表重复(在任何上下文中)。

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

# Perl 5 
    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 

.. … 两个点或三个点,范围操作或 flipflop 运算符 #

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

在列表上下文中,.. 是熟悉的范围运算符。 Perl 5 代码的范围不应该要求翻译。

在标量上下文中,..... 是鲜为人知的 Flipflop 运算符。 它们已被 fffff 取代。

字符串插值 #

在 Perl 5 中,"${foo}s" 从其旁边的常规文本中删除变量名。 在 Raku 中,只需将花括号扩展为包括sigil:"{$foo}s"。 事实上,这是插入表达式的一个非常简单的例子。

复合语句 #

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

条件语句 #

if elsif else unless #

大部分没有变化; 条件周围的括号现在是可选的,但如果使用,则不能立即跟随关键字,否则它将被视为函数调用。 将条件表达式绑定到变量也有一点不同:

if (my $x = dostuff()) {...}  # Perl 5 
if dostuff() -> $x {...}      # Raku 

(您仍然可以在 Raku 中使用我的表单,但它将扩展到外部块,而不是内部。)

除非条件仅允许 Raku 中的单个块;它不允许使用 elsif 或 else 子句。

given-when #

给定时构造类似于 if-elsif-else 语句链或类似于例如 switch-case 构造。 C. 它具有一般结构:

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 语句中与 $_ 匹配,这是由给定的设置。更一般地说,匹配实际上是 $_ 上的智能匹配,这样可以使用更复杂的实体(如regexp)进行查找而不是标量值。

另请参阅上面的smartmatch op上的警告。

循环 #

while until #

大部分没有变化;条件周围的括号现在是可选的,但如果使用,则不能立即跟随关键字,否则它将被视为函数调用。将条件表达式绑定到变量也有一点不同:

while (my $x = dostuff()) {...}  # Perl 5 
while dostuff() -> $x {...}      # Raku 

(您仍然可以在 Raku 中使用我的表单,但它将扩展到外部块,而不是内部。)

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

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

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

while (<IN_FH>)  { } # Perl 5 
for $IN_FH.lines { } # Raku 

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

do while/until #

# Perl 5 
do {
    ...
} while $x < 10;
 
do {
    ...
} until $x >= 10;

该构造仍然存在,但是 do 被重命名为 repeat,以更好地表示构造的作用:

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

for foreach #

首先要注意关于 forforeach 关键字的这种常见误解:许多程序员认为他们区分C风格的三表达形式和列表迭代器形式;他们不!事实上,关键词是可以互换的; Perl 5 编译器在括号中查找分号以确定要解析的循环类型。

C 风格的三因子形式现在使用 loop 关键字,否则保持不变。括号仍然是必需的。

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

循环迭代器表单以Raku命名,foreach不再是关键字。 for循环具有以下规则:

  • 括号是可选的;
  • 迭代变量(如果有的话)已经从列表前面出现,再出现在列表和添加的箭头操作符之后;
  • 迭代变量现在总是词法的:my 既不需要也不允许;
  • 迭代变量是当前列表元素的只读别名(在 Perl 5 中它是一个读写别名!)。如果需要读写别名,请将迭代变量前面的 -> 更改为 <->。从 Perl 5 进行翻译时,检查循环变量的使用以确定是否需要读写。
for my $car (@cars)  {...} # Perl 5; read-write 
for @cars  -> $car   {...} # Raku; read-only 
for @cars <-> $car   {...} # Raku; read-write 

如果正在使用默认主题 $_,那么它也是读写的。

for (@cars)      {...} # Perl 5; $_ is read-write 
for @cars        {...} # Raku; $_ is read-write 
for @cars <-> $_ {...} # Raku; $_ is also read-write 

在每次迭代中可以使用列表中多个元素,只需在箭头操作符后指定多个变量:

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

each #

这是 Perl 5 的 while…each(%hash) or while…each(@array) 的等价物,(即迭代数据结构的键/索引和值)而 Raku 中:

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

控制流语句 #

没发生变化的:

  • next
  • last
  • redo

continue #

不再有 continue 块了。而是在循环体内使用 NEXT 块(phaser)。

# Perl 5 
    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 ~= ':'
        }
    }

请注意,phasers 并不需要块。当您不想要另一个作用域时,这非常方便:

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

函数 #

带有裸块的内置函数 #

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

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

delete #

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

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

exists #

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

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

正则表达式 (regex/regexp) #

=~ 和 !~ 变成了 ~~ 和 !~~ #

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

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

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

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

捕获从 0 开始而非从 1 开始 #

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

移动修饰符 #

将任何修饰符从正则表达式的末尾移动到开头。这可能需要您在 /abc/ 这样的普通匹配上添加可选的 m

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

添加 :P5 或 :Perl5 副词 #

如果实际的正则表达式很复杂,您可能希望通过添加 P5 修饰符来原样使用它。

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

请注意,Perl 5 正则表达式语法可以追溯到很多年前,可能缺少自 Raku 项目开始以来添加的功能。

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

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

对于字符类,这意味着:

  • [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 ]

最长 token 匹配(LTM) 取代了备选分支 #

在 Raku 正则表达式中,| 遵循 LTM,它根据一组规则决定哪个备选分支赢得了一个模糊匹配,而不是先写出哪个。

解决这个问题最简单的方法就是在你的 Perl 5 正则表达式中把任何 | 更改为 ||

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

命名捕获 #

这些工作方式略有不同;他们也只使用最新版本的 Perl 5。

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

非捕获组中的内容用于实现捕获后面的内容,直到组的末尾(the)。捕获转到带有捕获名称的键下的 %+ 哈希。在 Raku 中,命名捕获以这种方式工作

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

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

注释 #

与 Perl 5 一样,注释在正则表达式中照常工作。

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

BEGIN, UNITCHECK, CHECK, INIT 和 END #

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

UNITCHECK 变为 CHECK #

Raku 中目前没有直接等效的 CHECK 块。Raku 中的 CHECK phaser 与 Perl 5 中的 UNITCHECK 块具有相同的语义:只要它出现的编译单元完成解析,它就会运行。这被认为是比 Perl 5 中 CHECK 块的当前语义更加理智的语义。但出于兼容性原因,不可能在 Perl 5 中更改 CHECK 块的语义,因此在 5.10 中引入了 UNITCHECK 块。因此决定 Raku CHECK phaser 将遵循更健全的 Perl 5 UNITCHECK 语义。

不再需要块 #

在 Perl 5 中,这些特殊块必须具有花括号,这意味着单独的范围。在 Raku 中,这不是必需的,允许这些特殊块与周围的词法范围共享它们的范围。

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

关于预编译改变了语义 #

如果将其放在正在预编译的模块中,则这些步骤将仅在预编译期间执行,而不是在加载预编译模块时执行。 因此,当从 Perl 5 移植模块代码时,您可能需要更改 BEGINCHECK

编译指令 #

strict #

严格模式现在默认启用。

warnings #

警告现在默认开启。

目前 no warnings 还未实现 ,但是把东西放在一个安静的 {} 中会让其沉默。

autodie #

autodie 更改以在异常时抛出异常的函数现在通常默认返回 Failures。您可以毫无问题地测试失败的定义/真实性。如果以任何其他方式使用 Failure,则将抛出由 Failure 封装的 Exception

# Perl 5 
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 
}

base, parent #

在类声明中,is 关键字在 Raku 中替换了 use baseuse parent

# Perl 5 
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 5 
constant DEBUG = 0;      # Raku 
use constant pi => 4 * atan2(1, 1); # Perl 5 
tau, pi, e, i; # built-in constants in Raku 
τ, π, 𝑒        # and their unicode equivalents 

编码 #

允许您以非 ascii 或非 utf8 编写脚本。 Raku 目前仅使用 utf8 作为其脚本。

整数 #

Perl pragma 使用整数运算而不是浮点运算。在 Raku 中没有这样的等价物。如果你在计算中使用原生整数,那么这将是最接近的事情。

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

lib #

处理在编译时查找模块的位置。底层逻辑与 Perl 5 非常不同,但在使用等效语法的情况下,在 Raku 中 use lib 与 Perl 5 中的相同。

mro #

不再相关。

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

say Animal.^mro;    # .^ indicates calling a meta-method on the object 

uft8 #

不再相关:在 Raku 中,源代码应该采用 utf8 编码。

vars #

在 Perl 5 中不鼓励使用。 参阅 https://perldoc.perl.org/vars.html

在转换为 Raku 之前,您应该重构 Perl 5 代码以消除 use vars 的需要。

命令行标记 #

请参阅 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 metasyntactic 选项。

  • -s

切换解析现在由 MAIN 子例程的参数列表完成。

# Perl 5 
    #!/usr/bin/perl -s 
    if ($xyz) { print "$xyz\n" }
./example.pl -xyz=5
5
# Raku 
    sub MAIN( Int :$xyz ) {
        say $xyz if $xyz.defined;
    }
raku example.p6 --xyz=5
5
raku example.p6 -xyz=5
5
  • it

被移除了

  • -P -u -U -W -X

被移除了,参阅 S19#Removed Syntactic Features.

  • -w

现在是默认行为。

  • -s, -T

这已被淘汰。 Reddit 讨论了几种复制“污点”模式的方法

文件相关的运算符 #

将文本文件的行读入数组 #

在 Perl 5 中,读取文本文件行的常用习惯用法如下:

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

在 Raku 中,这已经简化为

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

不要试图尝试在文件中进行 slurping 并将结果字符串拆分为换行符,因为这会给出一个带有尾随空元素的数组,这比你预期的要多一些(它也更复杂),例如:

# 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 文件,那么你可以在 slurp 的结果上调用 lines 方法:

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

另外,请注意 $! 与 Raku 中的文件操作失败无关。一个 IO 操作无法返回失败而不是抛出异常。 如果要返回失败消息,则它本身就是失败,而不是 $!. 要做同样的事情,我需要检查并报告 Perl 5:

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

注意:$fh 而不是 $!. 现在,您可以将 $_ 设置为失败并使用 $_ 来消亡:

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

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

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

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

而在 Perl 5 中,你会这样做

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,因此不需要转义参数(参数直接传递)。如果你使用 shell 或 qqx,那么一切都会变成一个长字符串,然后传递给 shell。除非您非常仔细地验证您的参数,否则很有可能使用此类代码引入 shell 注入漏洞。

环境变量 #

Perl 模块库路径 #

在 Perl 5 中,为 Perl 模块指定额外搜索路径的环境变量之一是 PERL5LIB

$ PERL5LIB="/some/module/lib" perl program.pl

在 Raku 中,这是类似的,只需要改变一个数字!您可能已经猜到了,您只需要使用 PERL6LIB

$ PERL6LIB="/some/module/lib" raku program.p6

在 Perl 5 中,使用 ‘:’(冒号)作为 PERL5LIB 的目录分隔符,但在 Raku 中使用 ‘,’(逗号)。例如:

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

但是

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

(Raku 无法识别 PERL5LIB 或旧的 Perl 环境变量 PERLLIB。)

与 Perl 5 一样,如果未指定 PERL6LIB,则需要通过 use lib pragma 指定程序中的库路径:

use lib '/some/module/lib'

请注意,PERL6LIB 在 Raku 中更具开发人员便利性(与 Perl5 中 PERL5LIB 的等效用法相反),模块消费者不应使用它,因为将来可能会将其删除。这是因为 Raku 的模块加载与操作系统路径不直接兼容。

Misc #

‘0’ 为真 #

与 Perl 5 不同,只包含零(‘0’)的字符串为 True。由于 Raku 具有核心类型,因此更有意义。这也意味着常见的模式:

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

在 Raku 中变的简单

... if $x;

dump #

不见了。

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

到目前为止,Rakudo 仅支持模块。

AUTOLOAD #

FALLBACK 方法提供类似的功能。

从模块导入特定函数 #

在 Perl 5 中,可以选择性地从给定模块导入函数,如下所示:

use ModuleName qw{foo bar baz};

在 Raku 中,通过使用相关子节点上的 is export 角色来指定要导出的函数;然后导出具有此角色的所有 subs。因此,以下模块 Bar 导出 subs foobar 但不导出 baz

unit module Bar;
 
sub foo($a) is export { say "foo $a" }
sub bar($b) is 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 5 行为呢?通过在模块内定义一个 EXPORT sub,它指定要导出的函数并删除 module Bar 语句。

以前的模块 Bar 现在只是一个名为 Bar.pm6 的文件,其中包含以下内容:

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 角色显式地导出 subs,而是通过 EXPORT sub 指定我们想要导出的模块中的 subs,然后我们填充一个包含实际将被导出的 subs 的哈希。 @import-list 由调用代码中的 use 语句设置,因此允许我们有选择地导入模块可用的 subs。

因此,要仅导入 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 sub,因此应该非常仔细地考虑是否值得编写自己的 EXPORT 例程。

从模块导入特定函数组 #

如果要从模块中导出函数组,只需要为组分配名称,其余的将自动运行。如果在 sub 声明中指定 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 trait 的所有子例程。

核心模块 #

Data::Dumper #

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

在 Raku 中,这些任务是使用 .perl 方法完成的,每个对象都有 .perl 方法。

# Given: 
    my @array_of_hashes = (
        { NAME => 'apple',   type => 'fruit' },
        { NAME => 'cabbage', type => 'no, please no' },
    );
# Perl 5 
    use Data::Dumper;
    $Data::Dumper::Useqq = 1;
    print Dumper \@array_of_hashes; # Note the backslash. 
# Raku 
say @array_of_hashes.perl; # .perl on the array, not on its reference. 

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

在 Raku 中,在变量的 sigil 前面放置一个冒号,将其转换为一个 Pair,其中包含 var 名称的键和 var 值的值。

# Given: 
    my ( $foo, $bar ) = ( 42, 44 );
    my @baz = ( 16, 32, 64, 'Hike!' );
# Perl 5 
    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 ].perl;
# OUTPUT: «["foo" => 42, "bar" => 44, "baz" => [16, 32, 64, "Hike!"]]» 

对于开发人员来说,还有一个特定于 Rakudo 的调试辅助工具,称为 dd(Tiny Data Dumper,它很小,它失去了“t”)。这将打印 STDERR 上给定变量的 .perl 表示和一些可以反省的额外信息:

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

Getopt::Long #

切换解析现在由 MAIN 子例程的参数列表完成。

# Perl 5 
    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 MAIN( Int :$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.p6
    24
    file.dat
    Verbosity off
raku example.p6 --file=foo --length=42 --verbose
    42
    foo
    Verbosity on
raku example.p6 --length=abc
    Usage:
      c.p6 [--length=<Int>] [--file=<Any>] [--verbose]

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

自动翻译 #

查找 Raku 版本的 Perl 5 构造的快速方法是通过自动翻译器运行它。

注意:这些翻译人员尚未完成。

蓝虎 #

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

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

Perlito #

在线翻译!

该项目是一套 Perl 交叉编译器,包括 Perl 5 到 6 的转换。它有一个 Web 前端,因此无需安装即可使用。到目前为止,它仅支持 Perl 5 语法的子集。

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

Perl-ToRaku #

Jeff Goff 为 Perl 5 设计的 Perl::ToRaku 模块是围绕 Perl::Critic 的框架设计的。它旨在将 Perl5 转换为可编译(如果不一定运行)的 Raku 代码,只需进行最少的更改。代码转换器是可配置和可插拔的,因此您可以创建和贡献自己的转换,并根据自己的需要定制现有的转换。您可以从 CPAN 安装最新版本,也可以在 GitHub 上实时关注项目。在线转换器可能在某些时候可用。

其他翻译知识来源 #