Perl 6 語言概述

沙棗發表於2015-04-10

本文主要參考Learn X in Y minutes 翻譯而來:

Perl 6 是為未來百年而預備的充滿才華,特性豐富的程式語言。

Perl 6 現在的發行版 Rakudo 已經是文件豐富的發行版了。

注:本文程式碼的輸出以 #=> 開始。

註釋

單行註釋以井號開始:

# Single line comment start with a pound

多行註釋可以使用左對齊的 =begin flag 直到另外一行左對齊的 =end flag 橫跨多行

=begin comment
Mulitiline comment
start with =begin flag
end with =end flag 
=end comment

變數

在 Perl 6 中,我們可以使用 my 來宣告一個區域性變數,用 our 來宣告一個全域性變數:

my $variable;

Perl 6 有四種型別的變數:

標量

通常表示單個的值。使用美元符號 $ 開始:

my $str = 'String';

雙引號可以允許變數內插:

my $str2 = "This is $str";

變數名稱可以包含單引號 ' 和減號 -, 但不能以它們結尾:

my $weird'variable-name_ = 5; #=> 合法的變數名

Perl 6 的使用 TrueFalse 表示布林值的真假:

my $bool = True;

我們可以使用前導的歎號 ! 來對這個布林值取反:

my $inverse = !$bool;

將一個值轉換成布林值,使用前置的 so:

 my $forced-bool = so $str;

列表

列表代表多個值的集合,名字以 @ 開始:

my @array = 'a', 'b', 'c';
my @array = ('a', 'b', 'c');
my @array = ['a', 'b', 'c'];

下面的寫法意思是一樣的:

my @letters = <a b c>; # 這和 Perl 5 的 qw, Ruby 的 %w 用法一樣

數字的列表:

my @array = 1, 2, 3;

陣列的索引從 0 開始,表示第三個元素用:

say @array[2];

陣列的內插用包括 [] 的完整列表名稱:

say "Interpolate an array using [] : @array[]";
#=> Interpolate an array using [] : 1 2 3

給列表的索引重新賦值:

 @array[0] = -1;

同時給多個索引賦值:

@array[0, 1] = 5, 6;

使用索引的列表給列表賦值:

my @keys = 0, 2;
@array[ @keys ] = @letters;
say @array;  #=> a 6 b

雜湊

雜湊是鍵值對 Pair 的集合.

my %hash = 1 => 2, 3 => 4;
my %hash = (1 => 2, 3 => 4);
my %hash = { 1 => 2, 3 => 4 };

雜湊的鍵 key 如果沒有空格,可以不使用引號包圍:

my %hash = autoquoted => "key",
           'some other' => 'value';

你可以直接用一個列表來給雜湊賦值:

my %hash = <key1 value1 key2 value2>;

這和下面的表示式是一樣的:

my %hash  = key1 => 'value1', key2 => 'value2';

還有一種冒號對 colon pair 的簡潔語法來表示雜湊的鍵值對,常用在函式或方法的命名引數中:

my %hash = :w(1),    # 等同於 w => 1
           :truey,   # 等同於 :truey(True) 或 truey => True
           :!falsey, # 等同於 :falsey(False) 或 falsey => False
           ;         # 在最後一個鍵值對後放置逗號是允許的

從雜湊中取出一個鍵的值使用 {}:

say %hash{'key1'}; #=> value1

如果鍵 key 是由不包含空格的字元數字組成,則可以用 <> 這種更簡潔的寫法:

say %hash<key2>;  #  %hash{key2} 這種寫法是錯誤的

函式

函式就是大多數語言中的過程或方法。

sub say-hello { say "Hello, world" }

將一個函式儲存在變數中,我們用 & 開始的變數:

my &s = &say-hello;

可以將一個匿名函式儲存成變數:

my &other-s = sub { say "Anonymous function!" }

函式的引數可以宣告資料型別,這種宣告檢查在程式碼編譯的時候能有效阻止一些難以覺察的錯誤:

sub say-hello-to(Str $name) { say "Hello, $name!" }

引數還可以是可選的:

sub with-optional( $arg? ) {  # 使用問號 `?` 來標記引數
    say "I might return (Any) if I don't have an argument passed,
           or I'll return my argument";
   $arg;
}

呼叫這個函式可以使用下面幾種寫法:

with-optional;      # 返回為空
with-optional();    # 返回為空
with-optional(1);  #  返回 1

可以在引數中設定預設的值:

sub hello-to( $name = "World" ) { say "Hello, $name !" }

hello-to;  #=> Hello, World !
hello-to(); #=> Hello, World!
hello-to('You'); #=> Hello, You !

當然可以用 冒號表示法 來書寫更簡潔的引數:

sub with-named( $normal-arg, :$named) { say $normal-arg + $named }

with-named(1, named => 6);  #=> 7
with-named(1, :named(6));     #=> 7

和可選引數相對,強制的引數用 !:

sub with-mandatory-named( :$str! ) { say "$str !" }
with-mandatory-named( str => "My String"); #=> My String !

當沒有引數或引數資料型別不對的時候,就會報錯:

with-mandatory-named;
#=> run time error: "Required named parameter not passed" 

with-mandatory-named(3);
#=> run time error: "Too many positional parameters passed"

當然可以使用命名的布林引數:

sub takes-a-bool( $name, :$bool ) { say "$name takes $bool" }
takes-a-bool('config', :bool);  #=>  config takes True
takes-a-bool('config', :!bool);  $=> config takes False

命名引數的預設值:

sub named-def( :$def = 5 ) { say $def }
named-def;  #=> 5
named-def(def => 15);  #=> 15

可以像 Perl 5 一樣,設定用星號 * 來表示 吞噬引數 (slurpy parameter) 來將後面的引數全部吸入:

sub as-many( $head, *@rest)  { say @rest.join('/') ~ "!" }
say as-many(1, 'Happy', 'Birthday'); #=> Happy/Birthday!

用於引數的一個非常有用的操作符,|, 通常用於展開列表:

sub concat-all( $a, $b, $c ) { say "$a $b $c" }
concat-all( |@array ); #=> a, b, c

像 Haskell 語言一樣,預設傳入 Perl 6 引數是被防寫的,不能修改:

sub immutate( $n ) { $n++ }
my $n = 1;
immutate($n);  #=> error

當然也可以修改引數:

sub mutate( $n is  rw ) { $n++ }
my $n = 1;
mutate($n);
say $n; #=> 2

如果不想被函式呼叫破壞變數的值,可以使用 copy 來對傳入的引數進行復制:

sub make-copy ($n is copy ) { $n++ }
my $n = 1;
make-copy($n);
say $n; #=> 1

條件語句

if True { say "It's true!" } #=> It's true!
unless False { "It's not false!" }  #=> It's not false!

下面的寫法多此一舉,而且會報錯:

if (True) say 'It is true!";

也可以把條件語句放在表示式的後面:

say "Quite truthy" if True;

也可以用 Perl 5 的三元表示式來進行條件判斷,但是用 ?? !!:

my $a = $condition ?? $value-if-true !! $value-if-false; 

Perl 6 的 given when 組合已經被 Perl 5 借鑑了:

given 'foo bar' {
    # $_ 的寫法和 Perl 5 是一樣的
     say $_; #=> foo bar
     when /foo/ { say "Yay!" } #=> Yay!
     when $_.chars > 50 { say "Quite a long string!" }
     default { say "Something else" } # 所有的 when 條件都不符合時,才觸發這個分支
}

迴圈結構

如果不設定條件或結束方法,loop 將會永遠的迴圈下去:

loop {
    say "This is an infinite loop!";
    last; # last 將會讓程式從迴圈中跳轉出去,就好像別的語言中的 break
}

loop (my $i = 0; $i < 5; $i++) {
    next if $i == 3; # next 將會讓程式直接跳到下一次迴圈,類似別的語言中的 continue
    say "This is a C-style for loop!";
}

for 語句接受一個列表,預設用 $_ 來作為迭代引數:

for @array {
    say "I've got $_";
    .say;  # 意思和 say $_; 相同
    $_.say;
}

也可以設定有名迭代變數(區域性):

for @array -> $a {
    next if $a == 3; # 跳轉到下次迴圈,迭代變數隨之變化
    redo if $a == 4; # 將重新一次迴圈,迭代變數不變
    last if $a == 5; # 結束迴圈
}

if unless 等語句中也可以使用 -> 設定有名的區域性變數:

if long-computation() -> $result { say "The result is $result" }

for 迴圈結構可以遍歷巢狀的列表:

for 1, 2, (3, (4, ((5)))) {
   say "Got $_.";
} #=> Got 1. Got 2. Got 3. Got 4. Got 5.

還有幾種形式的迴圈也經常用到:

map 常用於變換列表:

((1,2),3,(4,5)).map({ print "got $_," }) 
#=> got 1,got 2,got a,got 3,got 4,got 5,

pick 用於從列表中隨機獲取元素:

(@a, @b, @c).pick(1);
pick 1, @a, @b, @c;    # 和上面的用處一樣

操作符 (Operators)

Perl 6 定義了豐富的操作符,這些操作符其實就是函式,但寫法上更隨意,可以有效增加程式碼的可讀性。

操作符分成 5 種:

  • 前置型 (prefix), 如: ! => !True
  • 後置型(posfix), 如:++ => $a++
  • 中置型(infix),如:* => 4 * 3
  • 環繞型(circumfix),如:[-] => [1, 2]
  • 後置環繞型(post-circumfix),如:{-} => %hash{'key'}

比較操作符

  • == != > > <= >= 用於數字比較

    3 == 4;# False

    3 != 4; # True

  • eq ne lt le gt ge 用於字串比較

    'a' eq 'b'; # False

    'a' ne 'b' # True

    'a' !ne 'b'; # False

  • eqv 用於陣列的深度比較

    (1, 2) eqv (1, 3); # Flase

  • ~~ 是智慧匹配符,可用於多種型別的資料比較:

    'a' ~~ /a/; # True

    'key' ~~ %hash; # 如果雜湊中包含名稱為 ‘key' 的鍵,則返回 True

    $arg ~~ &bool-returning-function; # 如果函式可以接受 $arg 作為引數值,則返回真

    1 ~~ Int; # 物件的例項可以和物件的型別進行匹配

    1 ~~ True; # 數字和字串都可以和邏輯真假進行匹配

範圍構造符 ..:

3 .. 7; # 3 到 7 所有的整數

^.. 的任意一側,表示不包括這側的數:

3 ^..^ 7; # 4 到 6 所有的整數

^N0 .. ^N 的縮寫:

^10;  # 表示 0 到 9 所有的整數

.. 同樣可以構造惰性(lazy)和無限(infinite)列表:

my @array = 1 .. *; # 1 .. INF 同樣是表示 1 到無窮的序列

可以將這種範圍列表作為列表的索引:

say @array[^10]; 
#=> 1 2 3 4 5 6 7 8 9 10

當你在 REPL 中打出 say 1..* 的命令時,Perl 6 將認為這是一個無限迴圈, 會強制在輸出一定數量後停止.

這種寫法在賦值時顯得格外簡潔:

my @numbers = ^20;
say @numbers;
#=> 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

邏輯操作符 and or (也可以寫做: && ||):

3 && 4; # 和 3 and 4 相同,返回 4
0 || False; # 和 0 or False 相同,返回 False

&& 也稱為 短路 操作符,返回第一個計算結果為 False 的值,或最後一個 True 值。

解包 (Unpacking)

這種能力在賦值運算中顯得簡潔:

my ($a, $b) = 1, 2;
say $a; #=> 1
my ($, $, $c) = 1, 2, 3; # $ 作為一個佔位符(place-holder)
say $c; #=> 3
my ($head, *@tail) = 1, 2, 3; # `吞噬符`在賦值中也能用
say @tail; #=> 2 3

在函式定義中,[..] 可以用於列表解包:

sub foo(@array [$fst, $snd]) {
    say "My first is $fst, my second is $snd. All in all, I am @array[].";
}
foo([1,2]); #=> My first is 1, my second is 2. All in all, I am 1 2.

函式內部如果沒有 return 語句,那麼最後一個表示式的值將作為返回值:

sub next-index( $n ) { $n + 1 }
my $new-n = next-index(3); # $new-n 現在是 4

像大多數函式式語言一樣,Perl 6 的函式可以進行多次定義,每次的引數可以不同:

multi sub sayit(Int $n) { say "Number: $n" }
multi sub sayit(Str $s) { say "String: $s"; }

sayit("foo"); #=> String: foo
sayit(10);    #=> Number: 10
sayit(True);  #=> error! calling 'sayit' will never work with arguments of types

Perl 6 的函式還有很多特性,但不是很常用,詳細內容可到 http://doc.perl6.org 檢視。

變數生存期

和大部分動態語言類似,在使用變數前,需要先宣告它們.

Perl 6 定義了許多了宣告關鍵字,my, our, state, temp, my 定義了一個區域性的變數,這種變數在塊結構中尤其有用。

my $foo = 'foo';
sub foo {
    my $foo = 'Bar';
    sub bar {
        say "$foo $bar";
    }
    &bar; # 返回函式 bar 本身,並不會呼叫
}
foo()(); #=> 'Foo Bar'

從以上的程式碼中可以看出,變數 $foo, $bar 保留了上級結構中定義的值。 當我們想在最外層使用 $bar 的時候,就會現實 undefined value (未定義的值)。

Perl 6 有另外一種變數:動態變數(dynamic scope). 它們使用星號 * 開始.

my $*a = 1;

這種變數在 Lisp 的發展歷史中可以看到關於它的一些爭論,這種變數雖然不常用,但在某些場合 是必不可少的。

動態變數會在呼叫的時候才會從周圍環境去獲取當時的值,而不是在定義的時候就確定:

my $*foo = 1;
sub foo { say $*foo; }

動態變數是一種被函式呼叫的外部變數, 通常全域性變數都會被設定成動態變數。

our 定義的變數不是全域性的,而是包(package)範圍的。

temp 和 Perl 5 的 local 變數一樣,可以暫存上下文的變數,但不會修改它們。

state 宣告的變數,通常用在閉包或函式中:

my $hello = -> { state $var = 10; $var++ }
$hello(); #=> 10
$hello(); #=> 11

在閉包或函式中,靜態變數可以儲存呼叫過程中生成的中間結果,從而讓函式也可以擁有 狀態.

物件模型

Perl 6 擁有相當複雜的物件模型。

我們可以用 class 來宣告一個類,用 has 定義一個屬性,類方法用 method 定義。 屬性用 $!attr 定義為私有,用 $.attr 定義的屬性是公共的。每一個公共的屬性都 會自動構造一個同名的類方法 attr 來獲取類的屬性值。

Perl 6 的物件模型叫 SixModel 是非常靈活的,可以允許我們動態的新增方法,改變類的定義。

class A {
    # $.field 是不可變的
    # 想從內部修改它,使用 `$!field`
    has $.field;
    # rw 可以宣告一個可以修改的類屬性
    has $.other-field is rw;
    # 內部屬性
    has Int $!private-field = 10;

    method get-value {
        $.field + $!privated-field + $n;
    }

    method set-value($n) {
        # $.field = $n; # 這種寫法是錯誤的
        # 修改類屬性,應當用下面的寫法
        $!field = $n;

        # 類屬性是 rw 的話,就可以修改了
        $.other-field = 5;
    }

    method !private-method {
        say 'This method is private to the class!';
    }
};

# 建立一個類屬性 `$.field` 為 5 的類例項
# 不能從外部修改私有屬性
my $a = A.new(field => 5);
$a.get-value; #=> 18
# $a.field = 5; 將會報錯,因為 $.field 是隻讀的
$a.other-field = 10; # 公共屬性是 rw 的,可以被修改

Perl 6 支援多重繼承,雖然很多人對此有爭論:

class A {
    has $.val;

    # submethod 定義的方法不會被繼承
    submethod not-inherited {
        say "This method could not be inherited";
        say "submethod most useful for BUILD";
    }

    method bar { $.val * 5 }
}

# 從 A 繼承定義類 B
class B is A {
    method foo { say $.val; }

    # 將會覆蓋類 A 的同名方法 bar
    method bar { $.val * 10 }
}

my $b = B.new(val => 5);
$b.not-inherited; # 將會報錯,因為這個方法不能繼承
$b.foo; #=> 5
$b.bar; #=> 50

角色(Roles)

Perl 6 的 Roles 類似 Ruby 的 Mixins 和 Java 的 Interface, 是一種沒有例項的抽象類:

role PrintableVal {
    has $!counter = 0;
    method print {
        say $.val;
    }
}

想要從 Role 中繼承介面,就用 does:

class Item does PrintableVal {
    has $.val;

    # 從 Role 繼承後,Roles 中所有的私有屬性和方法都是可見的
    method access {
        say $!counter++;
    }
}

正規表示式

Perl 6 的正規表示式 Regex 是 Perl 5 的正規表示式 Regexp 的擴充套件,增加了很多有用的功能。

有一些東西沒有變,例如 ?, +*, 但有些東西就不同了,例如 | 的行為。

正規表示式中任意位置可以增加一些空格以增加程式碼的可讀性:

say so 'a' ~~ /a/; #=> True
say so 'a' ~~ / a /; #=> True

所有的正規表示式都會返回一個 Match 物件,這個物件可以像列表一樣取索引,也可以像 雜湊一樣查詢指定名稱的值,也可以返回字串格式的匹配結果。

返回的匹配結果儲存在一個區域性變數 $/ 中,你可以使用 $0, $1, ... 來獲取捕獲的值。

在 Perl 6 中,任何除了字母之外的字元都可能是含義不同的字元,所以如果想表示字元本身, 最好使用引號或者將其使用反斜槓 \ 轉義:

say so 'a|b' ~~ / a '|' b /; # True
say so 'a|b' ~~ / a \| b /;  # True

預設在 Regex 中,空格是被忽略的,如果想讓空格恢復其本意,要使用 :s:

say so 'a b c' ~~ / a b c /; # `False`
say so 'a b c' ~~ /:s a b c /; # `True`

數量表示符

? 表示匹配 0 或 1 個:

say so 'ac' ~~ / a b? c /; # `True`
say so 'abc' ~~ / a b? c /; # `True`

+ 表示匹配 1 或多個:

say so 'ac' ~~ / a b+ c /; # `False`
say so 'abc' ~~ / a b+ c /; # `True`
say so 'abbbbc' ~~ / a b+ c /; # `True`

* 表示匹配 0 或多個:

say so 'ac' ~~ / a b* c /; # `True`, they're all optional.
say so 'abc' ~~ / a b* c /; # `True`
so 'abbbbc' ~~ / a b* c /; # `True`
so 'aec' ~~ / a b* c /; # `False`. "b"(s) are optional, not replaceable.

** 表示指定數量範圍的個數

say so 'abc' ~~ / a b ** 1 c /; # `True`
say so 'abc' ~~ / a b ** 1..3 c /; # `True`
say so 'abbbc' ~~ / a b ** 1..3 c /; # `True`
say so 'abbbbbbc' ~~ / a b ** 1..3 c /; # `False`
say so 'abbbbbbc' ~~ / a b ** 3..* c /; # `True`

<[]> 表示字元類

字元類相當於 Perl 5 的 [],但這種寫法可以讓字元類進行一些簡單的運算:

say 'fooa' ~~ / f <[ o a ]>+ /; #=> 'fooa'

字元類中用 .. 表示範圍,就像 PCRE 中的破折號 -

say 'aeiou' ~~ / a <[ e..w ]> /;
#=> 'aeiou'

除了 a..z A..Z 0..9 外的所有字元,都需要轉義才能表示其本身, 包括空格:

say 'he-he !' ~~ / 'he-' <[ a..z \! \  ]> + /;
#=> 'he-he !'

如果將重複的字元放在一個字元類中,就會報錯:

'he he' ~~ / <[ h e ' ' ]> /;
#=> Warns "Repeated characters found in characters class"

想要對字元類取反,用 <-[...]>(PCRE 中用 [^]):

say so 'foo' ~~ / <-[ f o ]> + /;
#=> False

在字符集之間,- 表示差集,+ 表示並集:

say so 'foo' ~~ / <[ a..z ] - [ f o ]> + /;
#=> False
say so 'foo' ~~ / <-[ a..z ] + [ f o ]> + /;
#=> True
say so 'foo!' ~~ / <-[ a..z ] + [ f o ]> + /;
#=> True

分組和捕獲

分組:我們可以用 [] 將正規表示式分組( 像 Perl 5 中的 (?:):

say so 'abc' ~~ / a [ b ] c /; # `True`
say so 'fooABCABCbar' ~~ / foo [ A B C ] + bar /; #=> True

捕獲:我們可以用 () 來捕獲匹配到的字串:

say so 'fooABCABCbar' ~~ / foo ( A B C ) + bar /;
#=> `True`

匹配結果儲存在區域性變數 $/ 中,可以像列表一樣使用:

say $/[0];
#=> 「ABC」 「ABC」 (這是 `Match` 物件的表示形式)
say $0; #=> 「ABC」 「ABC」 (同上)

分支匹配符 |||:

|| 分支符同 PCRE 的 | 的效果相同,都是取第一個符合條件的分支:

'foo' ~~ / (fo || foo) /; say $0; #=> 「fo」

| 分支符是別的語言沒有的,它將選擇所有匹配分支中最長的字串的那個分支:

'foo' ~~ / (fo | foo) /; say $0; #=> 「foo」

包(package)

包是組織程式碼的形式,是一種名稱空間(namespace), Perl 6 一共有 6 種形式的包, 分別是 class, module, regex, role, subsetenum. package 是最底層的實現。

class Package::Name::Here  {}
module Hello::World { }
module Parse::Text; # 通常位於檔案第一行
grammar Parse::Text::Grammar { }

呼叫一個類,模組,可以用 use:

use JSON::Tiny; # 預設會匯出 from-json 這個函式
say from-json('[1]').perl; #=> [1]

當然你也可以用下面的方式呼叫:

my $actions = JSON::Tiny::Actions.new;

所有的 package 型別(class, role, etc) 預設都是全域性的,當然你也可以定義區域性的包:

定義一個可以匯出變數和方法的模組:

module Foo::Bar {
  our $n = 1; 
  our sub inc {
    our sub available { 
      say "Don't do that. Seriously. You'd get burned.";
    }
    my sub unavailable { 
      say "Can't access me from outside, I'm my !";
    } 
   }
   say ++$n;
 }
 say $Foo::Bar::n; #=> 1
 Foo::Bar::inc; #=> 2
 Foo::Bar::inc; #=> 3

常量

可以用 constant 定義一個全域性的常量:

constant Pi = 3.14;
constant $var = 1;

(完)

相關文章