[譯][Perl 6] 5to6-nutshell

araraloren發表於2019-02-16

About

  • Perl 6 document

  • 翻譯

  • 本翻譯意在幫助理解原文,因個人水平有限,難免會有翻譯不當,如果疑惑請在評論中指出。

Translation

原文

從 Perl 5到 Perl 6

簡而言之,以前做什麼現在怎麼做
吾欲以 Perl 6行 Perl 5之所為,當何如?

這個頁面嘗試去索引從 Perl 5到 Perl 6的語法以及語義上的變化。那些在 Perl 5中正常工作的,在 Perl 6中卻要換一種寫法的語法應該被列出在這裡(而許多 Perl 6的新特性以及風格就不會)。
因此,這個頁面並不是針對 Perl 6初學者的手冊或者進階文章,它是為有著 Perl 5知識背景的 Perl 6學習者以及移植 Perl 5 程式碼到 Perl 6的人員準備的技術文件(雖然Automated Translation可能會更方便一些)。
注意當文件中我們說“現在”時,更多的意思是“你現在正在學習的 Perl 6”,並沒有暗示說 Perl 5突然過時了,恰恰相反,大多數的我們都深愛 Perl 5,我們希望 Perl 5能再繼續使用好多年,事實上,我們的重要目標之一就是讓 Perl 5和 Perl 6之間順利互動。不過,我們也喜歡 Perl 6的設計目標,相比 Perl 5中的設計目標,更新穎,更好。所以,我們中的大多數人都希望在下一個十年或者兩個十年,Perl 6能成為更流行的語言,如果你想把“現在”當作未來,那也是很好的,但我們對導致爭論的非此即彼的思想不感興趣。

CPAN

參見Perl 6模組列表
如果你使用的模組還沒有被移植到 Perl 6,而且在列表中沒有替代品,可能在 Perl 6中的使用問題還沒有解決。
有很多目標是為了在 Perl 6中usePerl 5模組的工程:
v5模組意在讓Rakudo自身解析 Perl 5的程式碼,然後編譯成和 Perl 6同樣的位元組碼。
Inline::Perl5模組使用一個內嵌的perl直譯器執行 Perl 6指令碼中呼叫到的Perl 5程式碼。
其中,Inline::Perl5更完善,更有前途。

語法

-> 方法呼叫

如果你有讀過 Perl 6的程式碼,一個最明顯的地方就是方法呼叫語法現在使用點替代了箭頭:

$person->name; # Perl 5
$person.name; # Perl 6

點符號即容易書寫又更加符合行業標準,不過我們也想賦予箭頭別的使命(字串連線現在使用~運算子)。
對於直到執行時期才知道名字的方法呼叫:

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

如果你省略了雙引號,那麼 Perl 6會認為$methodname包含一個Method物件,而不是簡單的方法名字字串。

空白

Perl 5在允許空白的使用上有著驚人的靈活性,即使是在嚴格模式(strict mode)並且開啟警告(warnings)環境下:

# 不符合習慣,但是在 Perl 5是可以的
say "Hello".ucfirst  ($people
    [$ i]
    ->
    name)."!"if$greeted[$i]<i;

Perl 6也支援程式設計師書寫自由和程式碼的創造性,但是權衡語法靈活性和其一致性、確定性、可擴充套件的grammar,支援一次解析以及有用的錯誤資訊,整合如自定義運算子的特性,並且不會導致程式設計師誤用的設計目標,並且,“code golf”的做法也很重要,Perl 6的設計在概念上更簡潔而不是擊鍵上。
因此,在 Perl 5的語法的許多地方空白都是可選的,但在 Perl 6裡可能是強制的或者禁止的,這些限制中的許多不太可能關係到現實生活中的 Perl 程式碼(舉例來說,sigil 和變數名字之間的空格被禁止了),但是有一些可能不太幸運的和一些 Perl hacker的程式設計習慣風格相沖突:

  1. 引數列表的開括號之前不允許包含空白

    substr ($s, 4, 1);        # Perl 5 (這在 Perl 6 意味著傳遞了一個列表引數)
    substr($s, 4, 1);        # Perl 6
    substr $s, 4, 1;        # Perl 6 替代括號的風格
  2. 關鍵字後面要緊跟空白

    my($alpha, $beta);            # Perl 5,這在 Perl 6裡面將嘗試呼叫例程my()
    my ($alpha, $beta);            # Perl 6
    
    if($a < 0) { ... }            # Perl 5,在 Perl 6將會中止執行
    if ($a < 0) { ... }            # Perl 6
    if $a < 0 { ... }            # Perl 6,更符合習慣
    
    while($x-- > 5) { ... }        # Perl 5,在 Perl 6將會中止執行
    while ($x-- > 5) { ... }    # Perl 6
    while $x-- > 5 { ... }        # Perl 6,更符合習慣
  3. 字首運算子後面或者字尾運算子、字尾環繞運算子(包含陣列、雜湊下標運算子)的前面不允許包含空白。

    $seen {$_} ++;        # Perl 5
    %seen{$_}++;        # Perl 6
           方法呼叫運算子周圍不允許出現空白:
    $customer -> name;        # Perl 5
    $customer.name;            # Perl 6
  4. 中綴運算子在可能和存在的字尾運算子或者字尾環繞運算子衝突時前面要包含空白:

    $n<1;            # Perl 5 (在 Perl 6裡面可能和字尾環繞運算子 < > 衝突)
    $n < 1;            # Perl 6

然而,你可以使用unspace在 Perl 6的不允許使用空白的程式碼那增加空白:

#     Perl 5
my @books = $xml->parse_file($file)            # 一些註釋
                ->findnodes("/library/book");

#  Perl 6
my @books = $xml.parse-file($file)            # 一些註釋
                .findnodes("/library/book");

參見 Perl 6設計文件中的S03#Minimal whitespace DWIMmeryS04#Statement parsing

Sigils

在 Perl 5中,陣列和雜湊使用的 sigil 取決於怎樣訪問它們,在 Perl 6裡面,不論變數怎樣被使用,sigil 是不變的 – 你可以把他們作為變數名字的一部分(參見 Dereferencing)。

$-標量

sigil $ 現在總是和“標量”變數一起使用(比如$name),不再用於陣列索引以及雜湊索引。這就是說,你仍然可以使用$x[1]$x{"foo"},但是它們是作用在$x上,並不會對相似名字的@x%x有影響,它們現在可以通過@x[1]%x{"foo"}來訪問。

@-陣列

sigil @ 現在總是和“陣列”變數一起使用(比如@month@month[2]@month[2, 4]),不再用於雜湊值切片

%-雜湊

sigil % 現在總是和“雜湊”變數一起使用(比如%calories%calories<apple>%calories<pear plum>),不再用於陣列鍵值切片

&-過程

sigil & 現在始終(並且不再需要反斜槓了)引用一個命名子例程/運算子的函式物件並不會執行它們,換句話說,把函式名字當作“名詞”而不是“動詞”:

my $sub = &foo;    # Perl 5
my $sub = &foo;        # Perl 6

callback => sub  { say @_ };    # Perl 5 - 不能直接傳遞內建過程
callback => &say;                # Perl 6 - & 給出任何過程的“名詞”形式

因為 Perl 6一旦完成編譯就不允許在作用域內新增/刪除符號,所以 Perl 6並沒有與 Perl 5中undef &foo;等效的語句,並且最接近 Perl 5的defined &foo;defined &::("foo")(這使用了“動態符號查詢(dynamic symbol lookup)”語法)。然而,你可以使用my &foo;宣告一個可修改名字的子例程,然後在執行過程中向&foo賦值改變它。
在 Perl 5中,sigil &還可以用來以特定的方式呼叫子例程,這和普通的過程呼叫有些許不同,在 Perl 6中這些特殊的格式不再可用:

  1. &foo(…),使函式原型失效
    在 Perl 6中不再有原型了,對你來說,這和傳遞一個程式碼塊或者一個持有程式碼物件的變數作為引數沒有什麼不同:

    # Perl 5
    first_index {$_ > 5} @values;
    &first_index($coderef, @values);    # 禁止原型並且傳遞一個程式碼塊作為第一個引數
    
    # Perl 6
    first {$_ > 5}, @values, :k;    # :k 副詞使第一個引數返回下標
    first $coderef, @values, :k;
  2. &foo,還有 goto &foo; 用來重用呼叫者的引數列表或者替換呼叫棧的呼叫者

    sub foo { say "before"; &bar;     say "after" } # Perl 5
    sub foo { say "before"; bar(|@_) say "after" } # Perl 6 - 需要顯式傳遞
    
    # 建議使用Rakudo中的 .callsame
    
    sub foo { say "before"; goto &bar } # Perl 5
    
    # 建議使用Rakudo中的 .nextsame 或者 .nextwith

*-Glob

在 Perl 5中,sigil * 指向一個 GLOB 結構,繼而 Perl 可以使用它儲存非詞法變數,檔案控制程式碼,過程,還有格式(?formats)(不要和 Perl 5的用來讀取目錄中的檔名的內建函式 glob()混淆了)。
你最可能在不支援詞法檔案控制程式碼,但需要傳遞檔案控制程式碼到過程時的早期 Perl 版本程式碼中與 GLOB 邂逅:

# Perl 5 - 古法
sub read_2 {
    local (*H) = @_;
    return scalar(<H>), scalar(<H>);
}

open FILE, `<`, $path or die;
my ($line1, $line2) = read_2(*FILE);

在翻譯到適合 Perl 6的程式碼前,你可能需要重構你的 Perl 5程式碼以移除對 GLOB的依賴:

# Perl 5 - 詞法檔案控制程式碼的現代用法
sub read_2 {
    my ($fh) = @_;
    return scalar(<$fh>), scalar(<$fh>);
}
open my $in_file, `<`, $path or die;
my ($line1, $line2) = read_2($in_file);

然後這是可能的一個 Perl 6翻譯程式碼:

# Perl 6
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);

[]-陣列索引/切片

現在,陣列的索引和切片不再改變變數的sigil,並且在還可以使用副詞控制切片的型別:

  1. 索引

    say $months[2]; # Perl 5
    say @months[2]; # Perl 6 - @ 替代 $
  2. 值切片

    say join `,`, @months[6, 8..11]; # Perl 5, Perl 6
  3. 鍵值切片

    say join `,`, %months[6, 8..11];    # Perl 5
    say join `,`, @months[6, 8..11]:kv; # Perl 6 - @ 替代 %,並且使用 :kv 副詞

    還有注意下標括號現在並不是一個特殊的語法形式,而是一個普通的字尾環繞運算子,因此檢測元素是否存在刪除元素使用副詞完成。

{}-雜湊索引/切片

現在,雜湊的索引和切片不再改變變數的sigil,並且在還可以使用副詞控制切片的型別。還有,花括號中的單詞下標不再自動引用(即自動在兩邊加上雙引號),作為替代,總是自動引用其內容的新的尖括號版本是可用的(使用和qw//引用構造相同的規則):

  1. 索引

    say $calories{"apple"}; # Perl 5
    say %calories{"apple"}; # Perl 6 - % 替代 $
    
    say $calories{apple};    # Perl 5
    say %calories<apple>;     # Perl 6 - 尖括號,% 替代 $
    say %calories«$key»;    # Perl 6 - 雙尖括號內插作為一個 Str 列表
  2. 值切片

    say join `,`, @calories{`pear`, `plum`};  # Perl 5
    say join `,`, %calories{`pear`, `plum`};  # Perl 6 - % 替代 @
    say join `,`, %calories<pear plum>;          # Perl 6 - (更漂亮的版本)
    my $key = `pear plum`;
    say join `,`, %calories«$key»;              # Perl 6 - 在內插之後完成切分
  3. 鍵值切片

    say join `,`, %calories{`pear`, `plum`};    # Perl 5
    say join `,`, %calories{`pear`, `plum`}:kv; # Perl 6 - 使用 :kv 副詞
    say join `,`, %calories<pear plum>:kv;        # Perl 6 - (更漂亮的版本)

    還有注意下標花括號現在不再是一個特殊的語法形式,而是一個普通的字尾環繞運算子,因此檢測鍵是否存在鍵移除使用副詞完成。

引用的建立

在 Perl 5中,引用一個匿名陣列和雜湊以及過程的在它們建立的時候返回,引用一個存在的命名變數和過程使用運算子完成。
在 Perl 6中,匿名陣列和雜湊以及過程依然在它們建立的時候返回,引用一個命名的過程需在它們的名字前面加一個 sigil &,引用一個存在的命名變數使用item上下文:

my $aref = [1, 2, 9];            # 同時適用於 Perl 5&6
my $href = {A => 98, Q => 99 }; # 同時適用於 Perl 5&6

my $aref =         @aaa; # Perl 5
my $aref = item(@aaa); # Perl 6

my $href =         \%hhh; # Perl 5
my $href = item(%hhh); # Perl 6

my $sref =         &foo; # Perl 5
my $sref =          &foo; # Perl 6

解引用

在 Perl 5中,對一整個引用解引用的語法是使用 型別-sigil 和花括號,引用放在花括號裡面,在 Perl 6中,由花括號變為了圓括號:

# Perl 5
say         ${$scalar_ref};
say         @{$array_ref};
say keys    %{$hash_ref};
say         &{$sub_ref};

# Perl 6
say         $($scalar_ref);
say         @($array_ref);
say         %($hash_ref);
say         &($sub_ref);

注意在 Perl 5和 Perl 6中,圍繞的花括號或者括弧都經常被省略,雖然省略降低了易讀性。
在 Perl 5中,箭頭運算子->,用來單次訪問複合引用或者通過引用呼叫一個過程,在 Perl 6中,我們使用點運算子.完成這一任務:

# Perl 5
say         $array_ref->[7];
say         $hash_ref->{`fire bad`};
say         $sub_ref->($foo, $bar);

# Perl 6
say         $array_ref.[7];
say         $hash_ref.{`fire bad`};
say         $sub_ref.($foo, $bar);

在最近的 Perl 5版本(5.20或者以後),一個新的特性允許使用箭頭運算子解引用,參見實驗性的字尾解引用,這個新特性和 Perl 6中的.list以及.hash方法對應:

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

# Perl 6
my @a = $array_ref.list;        # 或者 @($array_ref)
my %h = $hash_ref.hash;            # 或者 %($hash_ref)
my @slice = $array_ref[3..7];

“Zen”切片可以有同樣的效果:

# Perl 6
my @a = $array_ref[];
my %h = $hash_ref{};

參見 S32/Containers

運算子

更多運算子細節請參見S03-operators
未改變的:

  • , 列表運算子

  • + 數值加法

  • – 數值減法

  • * 數值乘法

  • / 數值除法

  • % 數值求餘

  • ** 數值求冪

  • ++ 數值自增

  • — 數值自減

  • ! && || ^ 邏輯運算子,高優先順序

  • not and or xor 邏輯運算子,低優先順序

  • == != < > <= >= 數值比較

  • eq ne lt gt le ge 字串比較

<=> cmp 三目運算子

在 Perl 5,這些運算子返回 -1,0 或者 1,而在 Perl 6,它們返回值對應的是 Order::LessOrder::SameOrder::More
cmp 現在叫做 leg(字串比較),它只適用於字串上下問比較。
<=>仍然只適用於數值上下文。
cmp在 Perl 6中同時具備<=>leg的功能,這依賴於引數的型別。

~~ 智慧匹配運算子

運算子本身沒有改變,實際匹配的內容規則依賴於引數的型別,不過 Perl 6中的這些規則跟 Perl 5有很大不同。參見S03/Smart matching

& | ^ 字串位運算子 數值位運算子 邏輯運算子

在 Perl 5中,$ | ^的行為依賴於引數的內容,比如,31 | 33 的返回值和"31" | "33"是不同的。
在 Perl 6中,這些單字元運算子都被移除了,替代它們的是可以強制把引數轉換到需要的內容的雙字元運算子。

# 中綴運算子 (兩個引數,左右各一個)
+&    +|    +^    按位和    按位或    按位異或    :數值
~&    ~|    ~^    按位和    按位或    按位異或    :字串
?&    ?|    ?^    按位和    按位或    按位異或    :邏輯

# 字首運算子 (一個引數,在運算子後)
+!    非:數值
~!    非:字串
?!    非:邏輯 (作用和 ! 運算子一樣)

<< >> 數值左右移位運算子

+<+>取代。

say 42 << 3; # Perl 5
say 42 +< 3; # Perl 6

=> 胖逗號

在 Perl 5裡面,=>的行為就想一個逗號,但是會自動引用(即加上雙引號)左邊的引數。
在 Perl 6裡面,=>是 Pair 運算子,這是最主要的不同,但是在許多情況下行為都和 Perl 5裡面相同。
在雜湊初始化時,或者向一個期望雜湊引用的方法傳遞一個引數時,=>的用法就是相同的:

# 同時適用於 Perl 5&6
my %hash = (AAA => 1, BBB => 2);
get_the_loot(`diamonds`, {quiet_level => `very`, quantity => 9}); # 注意花括號

在為了不再引用(兩邊加上雙引號)列表的一部分而使用=>作為一種便利的方式時,或者向一個期望鍵,值,鍵,值這樣的平坦列表的方法傳遞引數時,繼續使用=>可能會破壞你的程式碼,最簡單的解決方法就是將胖逗號改為普通的逗號,然後在引用左邊的引數。或者,你可以將方法的介面改為接受一個雜湊,一個比較好的解決方式就是將方法的介面改為期望 Pair,然而,這需要你立即改動所有的方法呼叫程式碼。

# Perl 5
sub get_the_loot {
    my $loot = shift;
    my %options = @_;
    # ...
}
# 注意 這個方法呼叫中沒有使用花括號
get_the_loot(`diamonds`, quiet_level => `very`, quantity => 9);

# Perl 6,原始介面
sub get_the_loot($loot, *%options) { # * 意味著接受任何東西
    # ...
}
get_the_loot(`diamonds`, quiet_level => `very`, quantity => 9); # 注意 這個方法呼叫中沒有使用花括號

# Perl 6中,介面改為指定有效的選項
# sigil 前面的冒號代表期望一個 Pair
# 引數的鍵名字和變數相同
sub get_the_loot($loot, :$quiet_level?, :$quantity = 1) {
    # 這個版本會檢查未期望的引數
    # ...
}
get_the_loot(`diamonds`, quietlevel => `very`);    # 引數名字拼寫錯誤,將會丟擲一個錯誤

? : 條件運算子

現在使用兩個問號替代了原本的一個問號,冒號被兩個歎號替代了。

my $result = ($score > 60)    ?    `Pass`    :    `Fail`;    # Perl 5
my $result = ($score > 60)    ??    `Pass`    !!    `Fail`;    # Perl 6

. 連線運算子

被波浪線替代。
小貼士:想象一下使用針和線“縫合”兩個字串。

$food = `grape`    . `fruit`;    # Perl 5
$food = `grape`    ~ `fruit`;    # Perl 6

x 列表或者字串重複運算子

在 Perl 5,x是重複運算子。
在標量上下文,x將會重複一個字串,在 Perl 6裡,x會在任何上下文重複字串。
在列表上下文,x當且僅當將列表括入圓括號中時重複當前列表,在 Perl 6,新的運算子xx將會在任何上下文重複列表。
小貼士:xx相對於x是比較長的,所以xx適用於列表。

# Perl 5
print `-` x 80;            # 列印出一行橫槓
@ones = (1) x 50;        # 一個含有80個1的列表
@ones = (5) x ones;        # 把所有元素設為5

# Perl 6
print `-` x 80;            # 無變化
@ones = 1 xx 80;        # 不再需要圓括號
@ones = 5 xx @ones;        # 不再需要圓括號

.. … 雙點、三點、範圍以及翻轉運算子

在 Perl 5,..依賴於上下文是兩個完全不同的運算子。
在列表上下文,..是熟悉的範圍運算子,範圍運算在 Perl 6有許多新的改變,不過 Perl 5的範圍運算程式碼不需要翻譯。
在標量上下文,.....是鮮為人知的翻轉運算子,在 Perl 6中它們被fffff取代了。

字串內插

在 Perl 5,"${foo}s"將變數名從普通的文字中分離出來,在 Perl 6中,簡單的將花括號擴充套件到 sigil 外即可:"{$foo}s"

複合語句

條件語句

if elsif else unless

除了條件語句兩邊的括號現在是可選的,這些關鍵字幾乎沒有變化,但是如果要使用括號,一定不要緊跟著關鍵字,否則這會被當作普通的函式呼叫,繫結條件表示式到一個變數也稍微有點變化:

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

在 Perl 6中你可以繼續使用my格式,但是它的作用域不再位於語句塊的內部,而是外部。
在 Perl 6中unless條件語句只允許單個語句塊,不允許elsif或者else子語句。

given-when

given-when結構類似於if-elsif-else語句或者 C 裡面的switch-case結構。它的普通樣式是:

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

根據這個最簡單的樣式,有如下程式碼:

given $value {
    when "a match" {
        do-something();
    }
    when "another match" {
        do-something-else();
    }
    default {
        do-default-thing();
    }
}

這是在when語句中簡單的使用標量匹配的場景,更普遍的實際情況都是利用如正規表示式一般的複雜的實體等替代標量資料對輸入資料進行智慧匹配。
同時參閱文章上面的智慧匹配操作。

迴圈

while until

除了迴圈條件兩邊的括號現在是可選的,這些關鍵字幾乎沒有變化,但是如果要使用括號,一定不要緊跟著關鍵字,否則這會被當作普通的函式呼叫,繫結條件表示式到一個變數也稍微有點變化:

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

在 Perl 6中你可以繼續使用my格式,但是它的作用域不再位於語句塊的內部,而是外部。
注意對檔案控制程式碼按行讀取發生了變化。
在 Perl 5,這可以利用鑽石運算子在while迴圈裡面完成,如果使用for替代while則會有一個常見的bug,因為for導致檔案被一次性讀入,使程式的記憶體使用變的糟糕。
在 Perl 6,for語句是緩式的(lazy),所以我們可以在for迴圈裡面使用.lines方法逐行讀取檔案:

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

for foreach

首先注意這有一個常見的對forforeach關鍵字的誤解,許多程式設計師認為它們是把 C-風格for 和列表迭代方式區分開來,然而並不是!實際上,它們是可互換的,Perl 5的編譯器通過查詢後面的分號來決定解析成哪一種迴圈。
C-風格的for迴圈現在使用loop關鍵字,其它都沒有變化,兩邊的括號是必須的:

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

for或者foreach的迴圈迭代樣式現在統一叫做forforeach不再是一個關鍵字,括號是可選的。
迭代變數,如果有的話,已經從列表的前部移動到列表的後面,變數之前還有加上箭頭運算子。
迭代變數,現在是詞法變數,my已經不再需要而且不允許使用。
在 Perl 5裡,迭代變數是當前列表中元素的可讀寫別名。
在 Perl 6,這個別名預設是隻讀的(為了安全),除非你將->改為<->。當翻譯的時候,檢查迴圈變數的使用來決定是否需要可讀寫。

for my $car (@cars) { ... }    # Perl 5,可讀寫
for @cars -> $car    { ... }    # Perl 6,只讀
for @cars <-> $car    { ... }    # Perl 6,可讀寫

如果使用預設的變數$_,但是又需要可讀寫,那麼需要使用<->並顯示指定變數為$_

for (@cars)            { ... }        # Perl 5,預設變數
for @cars            { ... }        # Perl 6,$_ 是隻讀的
for @cars <-> $_     { ... }        # Perl 6,$_ 可讀寫
each

這是 Perl 6中和 Perl 5 while...each(%hash)或者while...each(@array)(遍歷資料結構的鍵或者索引和值)等同的用法:

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

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

流程控制

無變化的:

  • next

  • last

  • redo

continue

continue語句塊已經被去掉了,作為替代,在迴圈體中使用NEXT語句塊:

# Perl 5
my $str = ``;

for (1..5) {
    next if $_ % 2 == 1;
    $str .= $_;
}
continue {
    $str .= `:`;
}

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

函式

內建函式與裸露程式碼塊

內建函式之前接受一個裸露的程式碼塊,沒有逗號在其他引數之前,現在需要在塊和引數之間插入一個逗號,比如mapgrep等等。

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

delete

被轉換為{}雜湊下標運算子以及[]陣列下標運算子的副詞。

my $deleted_value = delete $hash{$key};    # Perl 5
my $deleted_value = %hash{$key}:delete;    # Perl 6,使用 :delete 副詞

my $deleted_value = delete $array[$i];    # Perl 5
my $deleted_value = @array[$i]:delete;    # Perl 6,使用 :delete 副詞

exist

被轉換為{}雜湊下標運算子以及[]陣列下標運算子的副詞。

say "element exists" if exists $hash{$key};    # Perl 5
say "element exists" if %hash{$key}:exists;    # Perl 6,使用 :exists 副詞

say "element exists" if exists $array[$i];    # Perl 5
say "element exists" if @array[$i]:exists;    # Perl 6,使用 :exists 副詞

正規表示式

由=~ !~變為~~ !~~

在 Perl 5對變數的匹配和替換是使用=~正則繫結運算子完成的。
在 Perl 6中使用智慧匹配運算子~~替代。

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

next if $line !~ /dynamic/;        # Perl 5
next if $line !~~ /dynamic/;    # Perl 6

$line =~ s/abc/123/;            # Perl 5
$line ~~ s/abc/123/;            # Perl 6

同樣的,新的.match方法以及.subst方法可以被使用。注意.subst是不可變操作,參見S05/Substitution

移動副詞

將所有的副詞的位置從尾部移動到了開始,這可能需要你為普通的匹配比如/abc/新增可選的m

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

增加 :P5 或者 :Perl5 副詞

如果實際的正規表示式比較複雜,你可能不想做修改直接使用,那麼就加上P5副詞吧。

next if $line =~    m/[aeiou]/;        # Perl 5
next if $line ~~ m:P5/[aeiou]/;        # Perl 6,使用 :P5 副詞
next if $line ~~    /<[aeiou]>/;    # Perl 6,新的風格

特殊的匹配語法通常屬於<>的語法

Perl 5的正則語法支援很多特殊的匹配語法,它們不會全部列在這裡,但是一般在斷言中作為()的替代的是<>
對於字元類,這意味著:

Perl 5                                        Perl 6
[abc]                                        <[abc]>
[^abc]                                         <-[abc]>
[a-zA-Z]                                    <[a..zA..Z]>
[[:upper:]]                                    <:Upper>
[abc[:upper:]]                                <[abc]+:Upper>

對於環視(look-around)斷言:

Perl 5                                        Perl 6
(?=[abc])                                    <?[abc]>
(?=ar?bitray* pattern)                        <before ar?bitray* pattern>
(?!=[abc])                                    <![abc]>
(?!=ar?bitray* pattern)                        <!before ar?bitray* pattern>
(?<=ar?bitray* pattern)                        <after ar?bitray* pattern>
(?<!=ar?bitray* pattern)                    <!after ar?bitray* pattern>
    (跟<>語法無關,環視語法`/fooKbar/`變成了`/foo<(bar)>/`
(?(?{condition})yes-pattern|no-pattern)        [ <?{condition}> yes-pattern | no-pattern ]

長字元匹配(LTM)取代擇一

在 Perl 6的正則中,|用於LTM,也就是根據一組基於模糊匹配規則從結果中選擇一個最優的結果,而不是位置優先。
對於 Perl 5程式碼最簡單解決方案就是使用||替代|
對於更多的規則,同時使用來自Blue Tigertranslate_regex.pl

編譯指示(Pragmas)

strict

嚴格模式現在是預設的。

warnings

警告現在預設是開啟的。
no warnings目前還是NYI狀態,但是把語句放到quietly {}塊之內就可以避免警告。

autodie

這個功能可以讓程式在發生錯誤丟擲異常,現在 Perl 6預設丟擲異常,除非你顯式的測試返回值。

# Perl 5
open my $i_fh, `<`, $input_path;    # 錯誤時保持沉默
use autodie;
open my $o_fh, `>`, $output_path;    # 錯誤時丟擲異常

# Perl 6
my $i_fh = open $input_path, :r;    # 錯誤時丟擲異常
my $o_fh = open $output_path, :w;    # 錯誤時丟擲異常

base

parent

現在use baseuse parent已經被 Perl 6中的is關鍵字取代,在類的宣告中。

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

# Perl 6
class Cat is Animal;

bigint bignum bigrat

不再相關。
Int現在是無限精度的,是Rat型別的分子(分母最大可以是2**64,出於效能考慮之後會自動轉換為Num型別)。如果你想使用無限精度分母的Rat,那麼FatRat顯然是最適合的。

constant

constant在 Perl 6用來變數宣告,這類似與my,不過變數會鎖定保持第一次初始化的值不變(在編譯時期求值)。
所以,將=>改為=,並加上一個 sigil。

use constant DEBUG => 0;    # Perl 5
constant    $DEBUG    = 0;    # Perl 6

use constant pi =>  4 * atan2(1, 1);    # Perl 5
# pi, e, i都是 Perl 6的內建變數

encoding

允許使用非ASCII或者非UTF8編碼編寫程式碼。

integer

Perl的編譯指示,使用整數運算替代浮點。

lib

在編譯時期操作 @INC。

mro

不再相關。
在 Perl 6中,方法呼叫現在使用 C3 方法呼叫順序。

utf8

不再相關。
在 Perl 6中,原始碼將使用utf8編碼。

vars

在 Perl 5中被取代,參見vars
在翻譯到 Perl 6程式碼之前,你可能需要重構你的程式碼移除對use vars的使用。

命令列標記

參見S19/commandline
未變化的:

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

-a

在Spec中沒有變化,但是在Rakudo中沒有實現。
現在你需要手動呼叫.split

-F

在Spec中沒有變化,但是在Rakudo中沒有實現。
現在你需要手動呼叫.split

-l

現在預設提供此行為。

-M -m

只有-M還存在,還有,大家可以不再使用“no Module”語法了,-M的“no”模組操作不再可用。

-E

因為所有的特性都已經開啟,請使用小寫的-e

-d,-dt,-d:foo,-D 等

++BUG元語法替代。

-s

開關選項解析現在被MAIN子方法的引數列表替代。

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

# Perl 6
    sub MAIN(Int :$xyz) {
        say $xyz if $xyz.defined;
    }
perl6 example.p6 --xyz=6
6
perl6 example -xyz=6
6

-t

現在還沒有指定。

-P -u -U -W -X

移除,參見S19/commandline

-w

現在預設開啟。

檔案相關運算子

按行讀取文字檔案到陣列

在 Perl 5,讀取文字檔案的行通常是這樣子:

open my $fh, `<`, "file" or die "$!";
my @lines = <$fh>;
close $fh;

在 Perl 6,這簡化成了這樣:

my @lines = "file".IO.lines;

不要嘗試一次讀入一個檔案,然後使用換行符分割,因為這會導致陣列尾部含有一個空行,比你想象的多了一行(它實際更復雜一些)。比如:

# 初始化要讀的檔案
spurt "test-file", q:to/END/;
first line
second line
third line
END
# 讀取
my @lines = "test-file".IO.slurp.split(/
/);
say @lines.elems; # 輸出 4

捕獲可執行檔案的標準輸出

鑑於 Perl 5你可能這麼做:

my $arg = `Hello`;
my $captured = `echo Q$argE`; # 注意反引號`
my $captured = qx(echo Q$argE);

或者使用String::ShellQuote(因為Q…E不是完全正確的):

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

在 Perl 6中你可能不想通過shell執行命令:

my $arg = `Hello`;
my $captured = run(`echo`, $arg, :out).out.slurp-rest;
my $captured = run(«echo "$arg"», :out).out.slurp-rest;

如果你想也可以使用shell:

my $arg = `Hello`;
my $captured = shell("echo $arg", :out).out.slurp-rest;
my $captured = qqx{echo $arg};

但是當心這種情況完全沒有保護,run不使用shell執行命令,所以不需要對引數進行轉義(直接進行傳遞)。如果你使用shell或者qqx,那麼所有東西都會作為一個長長的字串傳給shell,除非你小心的驗證你的引數,很有可能因為這樣的程式碼引入shell注入漏洞。

環境變數

perl模組路徑

在 Perl 5中可以指定 Perl 模組的額外的搜尋路徑的一個環境變數是PERL5LIB

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

在 Perl 6中也類似,僅僅需要改變一個數字,正如你想的那樣,你只需使用PERL6LIB

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

就 Perl 5來說,如果你不指定PERL5LIB,你需要使用use lib編譯指示來指定庫的路徑:

use lib `/some/module/lib`

注意PERL6LIB在 Perl 6中更多的是給開發者提供便利(與 Perl 5中的PERL5LIB相對應),模組的使用者不要使用因為未來它可能被刪除,這是因為 Perl 6的模組載入沒有直接相容作業系統的路徑。

雜項

`0`是 True

不像 Perl 5,一個只包含(`0`)的字串是 True,作為 Perl 6的核心型別,它有著更多的意義,這意味著常見的模式:

... if defined $x and length $x;    # 或者現代 Perl 的寫法 length($x)

在 Perl6裡面變的更為簡化:

... if $x;

dump

被移除。
Perl 6的設計允許自動的儲存載入編譯的位元組碼。
Rakudo目前只支援模組。

匯入模組的函式

在 Perl 5你可以像這樣有選擇性的匯入一個給定模組的函式:

use ModuleName qw{foo bar baz};

在 Perl 6,一個想要被匯出的函式需要對相關的方法使用is export,所有使用is export的方法都會被匯出,因此,下面的模組Bar匯出了方法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方法來指定匯出和刪除module Bar的宣告(注意在Synopsis 11中並沒有module語句,然而它可以工作) 。
模組Bar現在僅僅是一個叫做Bar.pm的檔案並含有以下內容:

use v6;

sub EXPORT(*@import-list) {
    my %exportable-subs = `&foo` => &foo, `&bar` => &bar,;
    my %subs-to-export;

    for @import-list -> $sub-name {
        if grep $sub-name, %exportable-subs.keys {
            %subs-to-export{$sub-name} = %exportable-subs{$sub-name};
        }
    }

    return %subs-to-export;
}
sub foo($a) { say "foo $a" }
sub bar($b) { say "bar $b" }
sub baz($z) { say "baz $z" }

注意現在方法已不再使用is export顯式的匯出,我們定義了一個EXPORT方法,它指定了模組可以被匯出的方法,並且生成一個包含實際被匯出的方法的雜湊,@import-list是呼叫程式碼中使用set語句設定的,這允許我們可選擇性的匯出模組中可匯出的方法。
所以,為了只匯出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"

注意,baz依然是不可匯出的即使使用use指定:

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

為了使這個能正常工作,顯然需要跨越許多的障礙,在使用標準use的情況下,通過使用is export指定某一個函式是匯出的,Perl 6會自動以正確的方式建立EXPORT方法,所以你應該仔細的考慮建立一個自己的EXPORT方法是否值得。

核心模組

Data::Dumper

在 Perl 5,Data::Dumper模組被用來序列化,還有程式設計師除錯的時候用來檢視程式資料結構的內容。
在 Perl 6中,這個任務完全被存在於每一個物件的.perl方法替代。

# 給定
my @array_of_hashes = (
    {NAME => `apple`, type => `fruit`},
    {NAME => `cabbage`, type => `no, plese no`},
);

# Perl 5
use Data::Dumper;
$Data::Dumper::Useqq = 1;
print Dumper @array_of_hashes;    # 注意反斜槓

# Perl 6
say @array_of_hashes.perl;    # .perl會作用在陣列而不是引用上面

在 Perl 5,Data::Dumper有著更復雜的可選的呼叫約定,它支援對變數命名。
在 Perl 6,將一個冒號放在變數的 sigil 前面轉換它為 Pair,Pair 的鍵是變數的名字,值是變數的值。

#給定
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 )],
);

# 輸出
    $foo = 42;
    $bar = 44;
    @baz = (
            16,
            32,
            64,
            `Hike!`
        );

# Perl 6
say [ :$foo, :$bar, :@baz ].perl;

# 輸出
    ["foo" => 42, "bar" => 44, "baz" => [16, 32, 64, "Hike!"]]

Getopt::Long

開關選項解析現在被MAIN子方法的引數列表替代。

# Perl 5
use 5.010;
use Getopt::Long;
GetOptions(
    `length=i`    =>    (my $length = 24),            # 數值
    `file=s`    =>    (my $data = `file.data`),    # 字串
    `verbose`    =>    (my $verbose),                # 標誌
) or die;
say $length;
say $data;
say `Verbosity`, ($verbose ? `on` : `off`) if defined $verbose;

perl example.pl
    24
    file.data
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.

# Perl 6
sub MAIN (Int :$length = 24, :file($data) = `file.data`, Bool :$verbose) {
    say $length    if $length.defined;
    say $data    if $data.defined;
    say "Verbosity", ($verbose ?? `on` !! `off`);
}
perl6 example.p6
    24
    file.dat
    Verbosity off
perl6 example.p6 --file=foo --length=42 --verbose
    42
    foo
    Verbosity on
perl6 example.p6 --length=abc
    Usage:
        example.p6 [--length=<Int>] [--file=<Any>] [--verbose]

注意 Perl 6會在命令列解析出錯時自動生成一個完整的用法資訊。

自動化翻譯

一個快速的將 Perl 5程式碼翻譯為 Perl 6程式碼的途徑就是通過自動化翻譯。
注意:這些翻譯器都還沒有完成。

Blue Tiger

本專案的目致力於自動現代化 Perl 程式碼,它沒有一個 web 前端,所以必須本地安裝使用,它還含有一個單獨的程式用來將 Perl 5的正則轉換為 Perl 6的版本。
https://github.com/Util/Blue_Tiger/

Perlito

線上翻譯器。
本專案是一套 Perl 跨平臺編譯器,包含 Perl 5到 Perl 6的翻譯,它有一個 web 前端,所以可以在不安裝的前提下使用,它目前只支援 Perl 5語法的一個子集。
http://www.perlito.org/perlito/perlito5to6.html

MAD

Larry Wall 自己用來翻譯 Perl 5到 Perl 6的程式碼已經太老了,在目前的 Perl 5版本中已經不可用了。
MAD(Misc Attribute Definition)是一個通過原始碼構建 Perl 的時候可用的配置選項, perl執行程式分析並翻譯你的 Perl 程式碼到 op-tree,通過遍歷 op-tree執行你的程式。通常,這些分析的細節會在處理的時候丟掉,當MAD開啟的時候,`perl`執行程式會把這些細節儲存成XML檔案,然後MAX解析器便可以讀取它並進一步處理成 Perl 6的程式碼。
為了進行你的MAD實驗,請前去 #perl6 頻道請教最適合的 Perl 5 版本。

Perl-ToPerl6

Jeff Goff的圍繞 Perl::Critic 框架的用於 Perl 5的模組 Perl::ToPerl6 ,目標是最最小的修改並將 Perl 5的程式碼轉換成可編譯的 Perl 6程式碼,程式碼的轉換器是可配置和外掛化的,你可以通過它建立自己的轉換器,根據你的需求自定義存在的轉換器。你可以從CPAN上面獲取最新的版本,或者 follow 釋出在 GitHub 的工程,線上轉換器可能在某一天會可用。

翻譯知識的其他來源

相關文章