2009
有用的和有意思的循環(huán)
讓我們來看一個基本的例子.
for 1, 2, 3, 4 { .say }
這是一個最簡單清晰的語法的例子.在這并沒有使用括號來包起整個列表的語句,象這種寫法可以貫穿整個 Perl 6. 通常比起 Perl 5 來你沒有必要寫那么多的括號了.
很象 Perl 5 , 這個循環(huán)中的值會默認存到 $_ .在這個方法調用的 say 其實就是 $_.say.注意在 Perl 6 中,你不能直接只打一個 say 而不加參數(shù),它會默認使用 $_ 來傳參.你需要使用 .say 。要么明確的指定是 $_.
下面這個語法塊并不是一個普通的塊.它能通過一個尖的指向,來告訴你的 循環(huán)變量傳進去的參數(shù)的名字 .
for 1, 2, 3, 4 -> $i { $i .say }
如果你調用的 return 內部有這個,將返回閉合的子函數(shù).
這個尖尖也能取 多個 參數(shù).象下面這樣.
1
2
3
for 1, 2, 3, 4 -> $i , $j { "$i, $j" .say }
# 1 2
# 3 4
實際做了些什么啦?就是你在列表進行迭代時一次取了兩個元素 . 如果你不明確指明參數(shù)的話,就退化到 Perl 5 一樣使用 $_.
我們可以意識到這個我們能做什么,比如迭代一個列表。當然,也可以是一個數(shù)組的值.
for @array { .say }
這是一個非常簡單的例子,我們可能更加喜歡使用 map:
@array.map : *.say;
如果對你來講 順序 和連續(xù)的并不重要,你可以使用 hyperoperator(超運算符),上一個文章中也講過這個,今天的主題也不詳細講這個了.
@array?.say;
我們也能使用 范圍構造器中綴操作符 .. 來生成一個數(shù)字的列表:
for 1..4 { .say }
有一個最通用的功能,就是我們想些生成一個從 0 開始到 $n 的數(shù)字的列表,比如常用的數(shù)組下標.我們可以寫成 0 .. $n-1或者另一個不同的范圍構造器 0..^$n.但在 Perl 6 中提供了一個短的快捷的方法就是使用前綴的 ^.
for ^4 { .say }
0
1
2
3
一個常用的理由是,人們在 Perl 5 中常常退回到 C 風格的循環(huán)的原因是必須知道 for 的成員數(shù)組中索引的位置,或者因為必須并行的迭代二個和更多的數(shù)組.Perl 6 提供了一個短的快捷方法,就是中綴的 Z 這個 zip 操作符.
for @array1 Z @array2 -> $one , $two { ... }
假設二個數(shù)組是相同的長度$one 會是第一個 @array1 的成員元素,$two 會是相應的位置 @array2 的成員元素.如果是不同的長度的話.迭代會停止到短的那個數(shù)組結束的長度.
我們可以很容易地在迭代數(shù)組包含進索引:
for ^Inf Z @array -> $index , $item { ... }
如果一個無限長的列表,會讓你害怕使用上面用法的話,可以象下面這樣,使用前綴操作符 ^ 來取出數(shù)組元素的長度.
for ^@array.elems Z @array -> $index , $item { ... }
上面這個可以得到相同的結果,但是更加優(yōu)雅.因為中綴操作符 Z 操作時,第一個元素的長度決定了什么整個長度.
for @array.kv -> $index , $item { ... }
@array.kv 會返回 keys 和 values 的交錯,這個 $key 是數(shù)組元素的下標.所以同時迭代這二個可能是你比較想要的效果.
希望這篇文章讓你了解 Perl 6 靈活的循環(huán)相關的一些概念,它們可以靈活的使用在各種常見任務上.在這之前,我要回答最后一個問題,我知道有人一直在想這個問題.怎么樣一次性迭代四個數(shù)組.
for @one Z @two Z @three Z @four -> $one , $two , $three , $four { ... }
這是一個關聯(lián)列表中綴操作符,這樣使用,是不是一種享受?
超運算符
pmichaud 在昨天介紹了 Perl 6 的 hyper 運算符,我這要進一步來探索 Perl6 中強大的元操作的特性.
首先,為簡單起見,我將編寫一個 lsay 的函數(shù),可以輕松地得到好看的列表值的輸出.這個 sub 是用我們用 Perl 來創(chuàng)建的
our sub lsay( @a ) { @a.perl.say }
接下來我們看 hyperoperator 的例子.在這個中,我們使用 >> 和 << 來替換 ? 和 ?, 主要因為這樣更加容易看(我怕我會需要眼鏡). ?和? 是語言中真實的形式,但較長的 ASCII 字符版本也是可以正常工作的.
首先.來個基本的:
添加兩個相同長度的列表
> (1, 2, 3, 4) <<+>> (3, 1, 3, 1)
4, 3, 6, 5
> (1, 2, 3, 4) >>+<< (3, 1, 3, 1)
4, 3, 6, 5
如果數(shù)組的長度是相同的,上面這兩種形式之間沒有區(qū)別.但是,如果長度是不同的:
> (1, 2, 3, 4) <<+>> (3, 1)
4, 3, 6, 5
> (1, 2, 3, 4) >>+<< (3, 1)
Sorry, lists on both sides of non-dwimmy hyperop are not of same length : left: 4 elements, right: 2 elements
這規(guī)則是, 象諸如此類的尖尖是用來表明 hyperoperator 使用時,當一端比另一端短,可以延長短的那一端來進行擴展延伸.
象如果是尖尖指向內部,是指不能進行擴展延伸.當然,還可以有各種組合都是可以的.所以你也能指出只有左邊能擴展延伸 (<<+<<),也可以只指出只有右邊能(>>+>>).當然也能二邊都是可以擴展延伸 (<<+>>),或者二邊都不能擴展延伸 (>>+<<). R 語言中也有向量的循環(huán)法則。
單標量擴展延伸如下:
> (1, 2, 3, 4) >>+>> 2
3, 4, 5, 6
> 3 <<+<< (1, 2, 3, 4)
4, 5, 6, 7
因此,這就是基本的使用中綴操作符 hyperoperator 的方法.您還可以使用前綴和后綴運算符:
單邊運算時,元素要能漏到操作符的左邊( 如@a>>++ )或右邊( 如 ~<< )。想象一下漏斗,總是從大的口向小的口漏。
所以操作符前面或后面接什么樣的超運算符要取決于操作數(shù)是在操作符的前面(用>>)或后面(用<<)
> ~<<(1, 2, 3, 4) # ~(1,2,3,4)
超運算符就是在普通運算符的后面,加強普通運算符的功能。如
> ~<< "1" , "2" , "3" , "4"
> -<<(1, 2, 3, 4)
-1 -2 -3 -4
> my @a = (1, 2, 3, 4);
@a>>++;
@a ; # 單邊運算時,@a與>>之間不能有空格,如不能寫成@a >>+
2, 3, 4, 5
你也能這樣:
> (0, pi/4, pi/2, pi, 2*pi)>>.sin # R 中向量化的運算
0, 0.707106781186547, 1, 1.22464679914735e-16, -2.44929359829471e-16
> (-1, 0, 3, 42)>>.Str
"-1" , "0" , "3" , "42"
這其實就是只是想說 >>. 是調用列表中的每一個成員的一種方法。點 (.)也是一個操作符
其他說明:hyperoperators 并不只是只能和內置操作符一起工作.他們也將能跟你定義以及任何新的運算符工作的很好(即大多數(shù)的的都能正常在現(xiàn)在的 Rakudo 上工作).
只要給放在適當?shù)牡胤?如@a >>/=>>2 整個數(shù)組成員都除以 2. 他們將來能和更多的結構一起工作,如多維列表,樹與哈希;我們可 S03 Hyper operators .(據(jù)我所知,有些功能還尚未在 Rakudo 正常實現(xiàn))
我并不知道是否有很多代碼示例中廣泛使用 hyperoperators. 但 LastOfTheCarelessMen’s Vector 是一個非常好的實現(xiàn).它使用單循環(huán)直接的實現(xiàn)了一個 N 維向量類.
reduce 和 hyper 元操作符
Hyper 亢奮的;精力旺盛的 Hyper[?ha?p?(r)]
今天是第四天,在這個小盒子中,你會見到一些有意思的實現(xiàn)階乘的函數(shù)
sub fac( Int $n ) {
[*] 1.. $n
}
Okay, 它是怎么工作的? 今天的 Advent 的盒子就是為了給你提供答案.
Perl 6 有一些不同的"元操作符"是用來修改現(xiàn)有的運算符完成更加強大的功能.
這個方括號中是一個有關“reduce metaoperator”的元操作符的例子,它是中綴運算符,會變成列表操作,操作是在后面各個元素的中間來, 例如,表達式
[+] 1, $a , 5, $b
它相當于
1 + $a + 5 + $b
這為我們提供了非常便利的機制“計算整個列表中的所有元素之和”:
$sum = [+] @a ; # @a 中所有元素之和
更多的中綴運算符(包含用戶自己定義的),都能放到這個方括號來減少操作符;
$prod = [*] @a ; # 相乘 @a 中所有的元素
$mean = ([+] @a ) / @a ; # 計算 @a 的平均值
$sorted = [<=] @a ; # 如果 @a 元素是數(shù)字排序就為 true
$min = [min] @a , @b ; # find the smallest element of @a and @b combined
在那個階乘的子函數(shù)中,表達示 [*] 1..$n 返回全部 1 到 $n 之間所有乘數(shù)的乘積.
另一個非常有用的元操作符是 "hyper" 操作符,放置 >>(與|或)<< 在操作符的二邊(一邊),使得那個操作 "hyper"(更亢奮).這個是用來操作列表中所有的成員,來進行這個包起來的運算符的操作.象下面的例子,我們來打算從 @a 和 @b 中成對的取出數(shù)據(jù)來進行運算后存入 @c.
@c = @a >>+<< @b ;
如果是在 Perl 5 中,我們需要寫成象才面這樣才能完成.
for ( $i = 0; $i < @a ; $i ++) {
$c [ $i ] = $a [ $i ] + $b [ $i ];
}
這只是有點長.
正如上面的方括號中,我們可以使用Hyper作用在各種運算符上,包括用戶定義操作符:
注:可以這樣記憶 << 和 >> 操作符,它們就像是漏斗,<< 讓元素從右邊漏入,>> 讓元素從左邊漏入,然后進行運算。
# 對 @xyz 中所有的元素進行 ++ 的操作
@xyz >>++
# 從@a 和 @b 中找出最小的元素放到 @x 中
@x = @a >>min<< @b ;
我們還可以翻轉<<的角度,使標量的行為像一個數(shù)組:
# @a 中每個成員都乘 3.5
my @a=2,4,6;
@b = @a >>*>> 3.5;
這其實相當于 @b = @a >>*<< (3.5,3.5,3.5) 較短的向量會被自動循環(huán)使用!模仿 R 語言的短向量自動循環(huán)。
如果右邊的向量沒有左邊的長,箭頭就指向那個單個向量。
# @x 中每個成員都乘以 $m 然后在加 $b
@y = @x >>*>> $m >>+>> $b ;
# 顛倒 @x 中所有的成員
@inv = 1 <</<< @x ;
# concatenate @last, @first to produce @full
@full = ( @last >>~>> ', ' ) >>~<< @first ;
> my @string=<I LOVE YOU>
I LOVE YOU
> @string >>~>>'-' >>~>> "szx"
I-szx LOVE-szx YOU-szx
>>~<< 兩側的元素個數(shù)必須相同!
當然,reductions 和 hyper 操作符也能聯(lián)合表達式
# 計算 @x 的平方和
$sumsq = [+] ( @x >>**>> 2);
還有很多其他元操作符,包括X(cross交叉),R(reverse反向),S(順序sequential).事實上,這只是在恰當?shù)奈恢梅艂€運算符,如+=,*=,?=,只是元形式的后綴等號運算,它相當于:
$a += 5; # same as $a = $a + 5;
$b = 7; # same as $b = $b 7;
$c min= $d ; # same as $c = $c min $d;
static types 和 multi subs.
打開Advent 這第三個盒子,這次我們要讀到什么啦?啊….真好.這次沒想到有二個禮物.這個盒子中放著 static types 和 multi subs.
在 Perl 5 中,$scalar 的標量只能包含二種東西引用或值,這值可以是任何東西,能是整數(shù),字符,數(shù)字,日期和你的名字.這通常是非常方便的,但并不明確.
在 Perl6 中給你機會修改標量的類型 .如果你這個值比較特別,你可以直接放個類型名在 my 和 $variable 的中間.象下面的例子,是在設置値一定要是一個 Int 型的數(shù)據(jù),來節(jié)約 cpu 判斷類型的時間,和讓你更少的程序上出錯.
my Int $days = 24;
其它的標量類型如下:
my Str $phrase = "Hello World" ;
my Num $pi = 3.141e0;
my Rat $other_pi = 22/7;
如果你還是想用老的設置值的方法,你可以不聲明類型或使用 Any 的類型代替.
今天盒子中的第二個禮物 multi subs 也很容易,因為我們會手把手教你.到底什么是 multi subs ? 簡單的來講 multi subs 可以讓我們 重載 sub 的名字 .當然 Multi subs 可以做更多其它的事情,所以下次其它作者的禮物中也會有這個,但現(xiàn)在我們會先介紹幾個非常有用的一些 sub .
multi sub identify(Int $x) {
return "$x is an integer.";
}
multi sub identify(Str $x) {
return qq<"$x" is a string.>;
}
multi sub identify(Int $x, Str $y) {
return "You have an integer $x, and a string \"$y\".";
}
multi sub identify(Str $x, Int $y) {
return "You have a string \"$x\", and an integer $y.";
}
multi sub identify(Int $x, Int $y) {
return "You have two integers $x and $y.";
}
multi sub identify(Str $x, Str $y) {
return "You have two strings \"$x\" and \"$y\".";
}
say identify(42);
say identify("This rules!");
say identify(42, "This rules!");
say identify("This rules!", 42);
say identify("This rules!", "I agree!");
say identify(42, 24);
還有兩個禮物很有優(yōu)勢吧.你可以嘗試多使用他們,我們會不斷的豐富這個 Advent 的樹,并不斷放更多的禮物,希望你能多來看看.
.comb your constraints
我們以前 advent 了解過的內容,對于今天所要介紹的禮物非常有用,今天要講兩個東西: comb 方法和 constraints 的概念。
constraints 和原來那章節(jié)中提到的靜態(tài)變量定義的相同,constraints 可以讓我們在寫程序的時候就更方便的在子函數(shù)和方法上進行控制.
在很多其它的程序中,你可以通過參數(shù)調用子函數(shù)并可以在參數(shù)進入的時候就通過 constraints 來驗證輸入的內容.這樣我們就能在程序聲明的時候就驗證輸入的內容,不在需要等到程序運行的時候.
下面是一個基本的例子,如果是一個整數(shù)和偶數(shù),在子函數(shù)中它會不能處理下去.在 Perl 5 中的實際基本就象下面這樣子了:
sub very_odd
{
my $odd = shift;
unless ($odd % 2)
{
return undef;
}
# 在這接著處理奇數(shù).
}
在 Perl 6 中,我們可以只需要簡單的:
sub very_odd(Int $odd where {$odd % 2})
{
# 在這接著處理奇數(shù).
}
如果你試圖來傳入一個偶數(shù)來調用 very_odd.你會直接得到一個 error.不要擔心:你可以使用 multi sub 的功能來給偶數(shù)一個機會:
multi sub very_odd(Int $odd where {$odd % 2})
{
# Process the odd number here
}
multi sub very_odd(Int $odd) { return Bool::False; }
我們在使用成對的 .comb 方法時,這個 constraints 是非常有用.
為什么正好是 .comb ? 當我們早上梳整我們的頭發(fā)時,我們先通常使用梳子來梳成你想要的樣子(線條),然后在你的頭上固定梳成的樣子.前面講的內容在這非常象.split.在這也一樣,你不是真想要切開字符串,而是你想達到一個什么樣目的.這一段簡單的代碼,來說明這兩種目標:
>say "Perl 6 Advent".comb(/<alpha>/).join('|');
P|e|r|l|A|d|v|e|n|t
>say "Perl 6 Advent".comb(/<alpha>+/).join('|');
Perl|Advent
正則表達式有可能另一天會拿出來講,但是我們先快速了解一下是沒有壞處的.這個第一行,會輸出 P|e|r|l|A|d|v|e|n|t. 它會取得每個字母然后放到一個暫時的數(shù)組中,然后使用 join 管道連接起來這是目的.第二行也有點象,但它捕獲了更多的字母,會輸出 Perl|Advent 這是第二個的目標單詞.
這個 .comb 是非常非常強大,然而,你得到你梳出來的輸出,你就能操作這個串了如果你有一個基本的ASCII十六進制字符的字符串,可以使用的 hyperoperators 超的操作符轉變各自的塊成為等效的 ASCII 字符!
say "5065726C36".comb(/<xdigit>**2/)?.fmt("0x%s")?.chr
# Outputs "Perl6"
say "5065726C36".comb(/<xdigit>**2/)
50 65 72 6C 36
**在正則里是量詞,表示重復前面的十六進制數(shù)兩次,合起來就是每兩個字符分一下。
如果你提心這個,你可以使用 .map 的方法:
say "5065726C36".comb(/<xdigit>**2/).map: { chr '0x' ~ $_ } ;
#Outputs "Perl6"
記的,這是 Perl.做任何事情都不只一種方法.
今天給完了所有禮物,我現(xiàn)在向你挑戰(zhàn).有 KyleHasselbacher 的協(xié)助,我們能使用約束.comb 和 .map 做出一個像樣的版本的古老的凱撒加密法.
use v6;
sub rotate_one( Str $c where { $c.chars == 1 }, Int $n ) {
return $c if $c !~~ /<alpha>/;
my $out = $c.ord + $n;
$out -= 26 if $out > ($c eq $c.uc ?? 'Z'.ord !! 'z'.ord);
return $out.chr;
}
sub rotate(Str $s where {$s.chars}, Int $n = 3)
{
return ($s.comb.map: { rotate_one( $_, $n % 26 ) }).join( '' );
}
die "Usage:\n$*PROGRAM_NAME string number_for_rotations" unless @*ARGS == 2;
my Str $mess = @*ARGS[0];
my Int $rotate = @*ARGS[1].Int;
say qq|"$mess" rotated $rotate characters gives "{rotate($mess,$rotate)}".|;
我希望你在休息的時候,可以使用目前為止在 Perl 6 中和今天的禮物中的學到的內容來編寫編碼算法.畢竟,編程語言本身只有更多的使用,才能讓它變的更優(yōu)秀.
一個正則表達式的故事
By perlpilot
在 advent 的第十天,我們有一個故事做為禮物……
曾幾何時,在比你想象的更近的時候,一個叫 Tim 的學 Perl 6 程序的學生,工作中出現(xiàn)了一個簡單的解析相關的問題.他的老板(我們叫他 C 先生)曾問過他,解析日志文件中包含著庫存信息,確保在文件內是唯一有效的行.文件中每行內是這樣的:
<part number> <quantity> <color> <description>
所以這個 Perl 6 的學生,他用熟悉正則表達式寫了一個可愛的小正則表達式,可以用來找出有效的行.代碼檢查每行內容是這樣寫的:
next unless $line ~~ / ^^ \d+ \s+ \d+ \s+ \S+ \s+ \N* $$ /
使用 ~~ 操作符的原因是因為,右側的正則表達式會匹配左側標量.在正則內部,^^ 是匹配行的開頭,\d+ 是用來匹配一個或者多個數(shù)字(由零件編號 part number 和數(shù)量 quantity 組成的),\S+ 是用來匹配一個或者多個非空白字符,
\N* 來匹配零個或者多個非換行符,\s+ 匹配空白之間的這些東西和 $$ 用來匹配行結束.
在 Perl 6 中,正則表達式的每個單獨的部分可以使用空格來讓它更具可讀性,所以更加好,這個空格不會是正則的一部分只用來分隔.
但 C 先生決定最好信息的每個部分都可以從提取來驗證. Tim 想了一下,“沒問題,我只要使用括號來捕獲”.下面就是全部需要做的:
next unless $line ~~ / ^^ (\d+) \s+ (\d+) \s+ (\S+) \s+ (\N*) $$ /
在成功的模式匹配以后,每個括號內都存著匹配到的對象本身($/),可以通過 $/[0],$/[1],$/[2] 或 $/[3].它可以通過特殊的變量 $0,$1,$2,$3訪問.Tim 和他老板 C 先生都很高興.
但隨后發(fā)現(xiàn)了一些行中,沒有從描述信息中給顏色信息分開,這些行其實也是有效的.在行中顏色信息和描述信息有個特殊的組合方式.他們總是象下面這樣:
<part number> <quantity> <description> (<color>)
在這像以前一樣,可以加入包括任意數(shù)量的空格在字符中. Tim 認為,“現(xiàn)在這個本來簡單的解析程序似乎突然更加復雜了”.幸運的是,Tim 可以找一個地方尋求幫助.他迅速登錄到 irc.freenode.org,加入 #perl6 通道 并請求大家協(xié)助.有人建議他使用名字來命名他的正則表達式的各個部分,來使事情變得更容易.然后使用交替的方法來匹配這個正則表達式的最后一部分的多種可能.
首先,Tim 嘗試給正則能捕獲到的每個部分都加上一個名字,詳細信息可以見 Perl 6 正則的綱要,下面是他所做的:
next unless $line ~~
/ ^^ $<product>=(\d+) \s+ $<quantity>=(\d+) \s+ $<color>=(\S+) \s+ $<description>=(\N*) $$ /
現(xiàn)在,成功的匹配后,每各部分都可以匹配到對象中不同的東西,通過特殊的變量 $<Product>,$<quantity>,$<color> 和 $<description>.
這比預期的更容易,讓 Tim 感到非常有信心.接著,他需要補充:交替區(qū)分兩種不同的有效行:
next unless $line ~~ / ^^
$<product>=(\d+) \s+ $<quantity>=(\d+) \s+
[
| $<description>=(\N*) \s+ '(' $<color>=(\S+) ')'
| $<color>=(\S+) \s+ $<description>=(\N*)
]
$$
/
為了從正則表達式中的交替和其余部分隔離開,Tim 使用了分組括號([ and ])在要交替檢查的部分.
這個分組是正則的一部分,其中像圓括號是唯一沒有捕捉到 $0 的, 由于必須匹配到精確的圓括號, Tim 使用了另一個有用的 Perl6 正則表達式的優(yōu)勢:帶引號的字符串字面匹配.因為分配給正則表達式的中 $<color> 和 $<description> 總是會在適當部分包含字符串.
Tim 非常的揚眉吐氣!他展示了他的代碼給 Mr.C,并表揚到 "干得好 Tim!";
然而,經過成功過后,Tim 開始以更挑剔的眼光來看他的工作.對于一些行中描述之后顏色,它有可能是 ( color) or (color ) or( color ).他目前正則表達式是正常的,但如果描述中包括的顏色的部分象前面一樣時,并不是所有匹配顏色的會設置 $<color>. Tim 初步修復,通過加入更多的 \s*:
next unless $line ~~ / ^^
$<product>=(\d+) \s+ $<quantity>=(\d+) \s+
[
| $<description>=(\N*) \s+ '(' \s* $<color>=(\S+) \s* ')'
| $<color>=(\S+) \s+ $<description>=(\N*)
]
$$
/
這運行的非常良好,但正則表達式的開始顯得有點凌亂.Tim 再次使用 #perl6 來讓人幫助.
這時候有個名叫 PerlJam 告訴他,“你為什么不把你的正則表達式放到 grammar 中?這可以讓你分配給每片到變量來匹配對象”“ Wha?? Tim 不知道 PerlJam 講的是什么.通過簡短的交流后,Tim 了解后,并知道在哪里查看必須的相關信息后.然后感謝 PerlJam,并在次回到了程序上.這一次的正則表達式幾乎消失,因為它使用了 grammar.什么是 grammar ?,看下面匹配的代碼:
grammar Inventory {
regex product { \d+ }
regex quantity { \d+ }
regex color { \S+ }
regex description { \N* }
regex TOP { ^^ <product> \s+ <quantity> \s+
[
| <description> \s+ '(' \s* <color> \s* ')'
| <color> \s+ <description>
]
$$
}
}
# ...在來到代碼開始的地方
next unless Inventory.parse($line);
以前的正則表達式中各自的變量變成了 grammar 中的命名正則表達式.在 Perl 6 的正則表達式中的命名正則是由括在尖括號內的名稱來匹配(< and >).當 Grammar.parse 調用來匹配一個標量時(會操作這特定的命名正則 TOP)行為是完全和以前一樣,因為命名的正則表達相當于其它正則表達式的一部分,匹配的文本保存到匹配對象中,并引用該名稱.
雖然仍然有改進的余地,Tim 和 Mr.C 對這個結果感到非常高興.
完
注:默認情況下,允許啟用空格注解; 所以,雖然在 Perl 5 中您可以用“hello there”本身來匹配“hello there”,但在 Perl 6 中,您必須將其改為 /hello <sp> there/.這樣就可以在正則表達中將條件清晰地分離開來.
Perl 6 正則表達式可以被復用.在匹配單一的詞時,復用正則表達式是很荒謬的;但在解析配置文件時,幾乎必須要復用正則表達式(這取決于配置文法的復雜度、發(fā)生修改的頻率等).這樣性能也會高很多.
在 Perl 5 中, Regexp::Common 模塊,已經在嘗試復用正則表達式,但是,因為 Perl 5 不允許復用正則表達式,所以不得不將它們封裝在一個模塊接口中. Perl 6 完全支持這種復用.
其它參數(shù)資料:
類, 屬性, 方法和其它
By jnthnwrthngtn
我非常興奮地撕下今天的禮物上閃亮的包裝紙,里面是無可爭議的 Perl 6 的對象模型,它內置了其類聲明,角色組成,自豪的元模型(meta-model).除了有先進的功能外,讓我們看看在 Perl 6 中是多么容易寫一個類.
class Dog {
has $.name;
method bark($times) {
say "w00f! " x $times;
}
}
我們開始使用一個 class 的關鍵字.如果你有學過 Perl5 的話,你能想到的類有點像包(package)的變種,這個關鍵字為您提供一個優(yōu)雅的語義.
接下來,我們使用 has 的關鍵字來聲明屬性訪問器方法.這個"."的東西名叫 twigil. Twigil 是用來告訴你指定變量的作用域.它是"屬性 + 存取方法"的組合.它的選項是:
has $!name; # 私有; 只能在內部可見
has $.name is rw; # Generates an l-value accessor
接下來是方法的使用,并介紹使用 method 的關鍵字.在對象中的方法象包中的子函數(shù),不同之處在于方法是放在類的方法列表的條目中.
它還能自動取得調用者(invocant),所以你如果沒有在參數(shù)列表中加入?yún)?shù).它是會給自我傳遞過去.在 Perl 5 中需要我們顯示的寫 $self = shift.
所有的類都繼承一個叫 new 的默認的構造器,會自動的映射命名參數(shù)到屬性,所有傳進的參數(shù)會存到屬性中.我們可以調用 Dog 的構造器(這個 Dog 的類的對象,并取得一個新的實例).
my $fido = Dog.new(name => 'Fido');
say $fido.name; # Fido
$fido.bark(3); # w00f! w00f! w00f!
請注意,Perl 6 中的方法調用操作符是"."而不是 Perl 5 中使用的"->".它縮短了 50% 并更加合適從其他語言轉過來的開發(fā)人員.
當然,很容易實現(xiàn)繼承,下面我們建一個叫 puppy 子類 ,直接使用 is 加父類的名字就行了.
class Puppy is Dog {
method bark($times) {
say "yap! " x $times;
}
}
這也支持委托,詳細作用見下面的 FQA.
class DogWalker {
has $.name;
has Dog $.dog handles (dog_name => 'name');
}
my $bob = DogWalker.new(name => 'Bob', dog => $fido);
say $bob.name; # Bob
say $bob.dog_name; # Fido
在這里,我們聲明指出我們想調用 DogWalker 類的名為 dog_name 的方法,并設置這個方法轉到 Dog 類中包含名為 name 的方法.重命名只是其中的一個可選方式;委托常常有很多其它的實現(xiàn)方法.
內心深層之美比外在更加重要.所以,在整潔的語法之下是使用 meta-model(元模型)想法來實現(xiàn)對象.類,屬性和方法都是 Perl 6 中最重要和 Meta-object 的.我們可以在運行時使用這些內省對象.
for Dog.^methods(:local) -> $meth {
say "Dog has a method " ~ $meth.name;
}
這個 .^ 的操作是 . 操作的變種,用來替換元類(metaclass-描述類的這個對象)的調用.在這里,我們提供該類所定義的方法(Method)的列表,我們使用 :local 來排除那些從父類的繼承. 這不只是給我們一個名字列表,而是方法對象的列表.其實我們直接使用這個對象來調用方法,但在這種情況下,我們只要它的名字就行.
讓你了解 Meta-programming 并附送一個擴展 Perl6 的對象的功能:只要你知道聲明一個方位,使用 method 的關鍵字讓它在編譯時在調用元類中的 add_method 來變成實際的方法.所以在 Perl 6 中,不僅為您提供了強大的對象模型,但也提供了機會,用來實現(xiàn)其它的特性,以滿足未來我們還沒有想到的需求.
這些都只是 Perl 6 的對象模型所提供的偉大的事情中的一些,也許我們會發(fā)現(xiàn)更多的東西在其他禮品中. :-)
注:
面向對象的概念
首先,我們定義幾個預備性的術語.
構造器 (constructor): 創(chuàng)建一個對象的函數(shù).
實例 (instance): 一個對象的實例化實現(xiàn).
標識 (identity): 每個對象的實例都需要一個可以唯一標識這個實例的標記.
實例屬性 (instance attribute): 一個對象就是一組屬性的集合.
實例方法 (instance method): 所有存取或者更新對象某個實例一條或者多條屬性的函數(shù)的集合.
類屬性(class attribute): 屬于一個類中所有對象的屬性,不會只在某個實例上發(fā)生變化.
類方法(class method): 那些無須特定的對性實例就能夠工作的從屬于類的函數(shù).
委托 (Delegation): 在對象需要執(zhí)行某個工作時并不直接處理,而是要求另一個對外象代為處理(有時只處理部分工作),所以這時第二個對象代表第一個對象來執(zhí)行該操作。
調用者(invocant): 對類來講,調用者是包的名字,對實例方法來講,調用者是指定對象的引用.換句話講,調用者就是調用方法的那種東西,有的文章叫他為代理(agent)施動者(actor).
抽象類(abstract class):抽象類實現(xiàn)類的占位符,主要用來定義行為,而子類用來實現(xiàn)這個行為。
arguments and parameters
By carl
在第9天的 advent 中…我打開了 …這是有關 parameters 和 arguments
你也許了解或者不了解 Perl5 的 是怎么處理函數(shù)參數(shù)的.先讓你看看,它通常象下面的這個例子這樣:
sub sum {
[+] @_
}
say sum 100, 20, 3; # 123
這個 [+] 是在 Perl 6 中的,但我們也可以寫成 Perl 5 風格的
my $i = 0;
$i _= $_ for @_;
$i;
我們要想到上面這些區(qū)別,這些在 Perl 6 中非常重要,也就是為什么我們講 Perl 6 比 Perl 5 好.當你調用函數(shù)時.你可以從 @_ 找到你的參數(shù).你然后取出它們來做一些操作.
這是非常靈活的.因為它不會對參數(shù)做任何默認的處理,程序會全部傳給你來進行處理.當然這也同樣是令人厭煩因為樣樣都要自己處理,但很方便我們來進行擴展進行參數(shù)的檢查,看下面這個虛構的例子.
sub grade_essay {
my ($essay, $grade) = @_;
die 'The first argument must be of type Essay'
unless $essay ~~ Essay;
die 'The second argument must be an integer between 0 and 5'
unless $grade ~~ Int && $grade ~~ 0..5;
%grades{$essay} = $grade;
}
(如果在 Perl 5 中,你需要使用 isa 來替換 ~~ 和使用 %grades 來替換成 $grades 才能正常工作.除了這些,都在 Perl6 中工作)
現(xiàn)在,這一刻,看看上面的內容,看到手冊中的參數(shù)驗證的實現(xiàn),你是不是開始有點絕望嗎?你感覺到了吧?好.
在 Perl 5 中的解決方法是使用優(yōu)秀的 CPAN 模塊,象 Sub::Signatures 和 MooseX::Declare,然后在你的程序中使用這些模塊,并按照模塊設置就行了.
在 Perl 6 的中的解決方法是,給你參數(shù)設置默認范圍. 我在想看了下面這些時, “請確保鍵盤前的你不會流口水”.在 Perl 6 中,我會寫這樣來寫子函數(shù):
sub grade_essay(Essay $essay, Int $grade where 0..5) {
%grades{$essay} = $grade;
}
現(xiàn)在我們見到,在這程序運行會對這個長版本的參數(shù)進行檢查,沒有必要在導入其它的 CPAN 的模塊了.
有時,我們可以提供一些默認的值給參數(shù):
sub entreat($message = 'Pretty please, with sugar on top!', $times = 1) {
say $message for ^$times;
}
如果這些參數(shù)的默認的值是不固定的,可以使用老的方式來傳參數(shù).
sub xml_tag ($tag, $endtag = matching_tag($tag) ) {...}
如果您的參數(shù)是不確定的,對這種可選的參數(shù)可以加一個 ? 的標記.
sub deactivate(PowerPlant $plant, Str $comment?) {
$plant.initiate_shutdown_sequence();
say $comment if $comment;
}
有一個特性,我特別喜歡,我們可以在調用時通過參數(shù)名字來引用參數(shù),這樣你可以以喜歡的任何順序傳遞命名參數(shù).這樣會永遠記得在這個函數(shù)中參數(shù)本來的順序:
sub draw_line($x1, $y1, $x2, $y2) { ... }
draw_line($x1, $y1, $x2, $y2); # phew. got it right this time.
draw_line($x1, $x2, $y1, $y2); # dang! :-/
這的方法是引用參數(shù)的名字,來使得這個問題被解決:
draw_line(:x1($x1), :y1($y1), :x2($x2), :y2($y2)); # works
draw_line(:x1($x1), :x2($x2), :y1($y1), :y2($y2)); # also works!
冒號的意思是 "這來自命名參數(shù)", 整個結構讀作:name_of_parameter($variable_passed_in).這可以使用的參數(shù)和變量具有相同的名稱,但有一個簡短形式:
draw_line(:$x1, :$y1, :$x2, :$y2); # works
draw_line(:$x1, :$x2, :$y1, :$y2); # also works!
我喜歡短形式.我覺得它使我的代碼更具可讀性.
如果作為 API 的作者,要強迫別人使用命名參數(shù) – 例如還是在 draw_line 的情況下 – 你只需要提供在子程序參數(shù)前的冒號.
sub draw_line(:$x1, :$y1, :$x2, :$y2 ) { ... } # optional nameds
但要小心注意,命名參數(shù)默認是可選的.換句話說,上述內容相當于:
sub draw_line(:$x1?, :$y1?, :$x2?, :$y2?) { ... } # optional nameds
如果你想明確地指出必需的參數(shù),可以追加!對下面的這些參數(shù):
sub draw_line(:$x1!, :$y1!, :$x2!, :$y2!) { ... } # required nameds
現(xiàn)在調用這個,就像他們是普通的順序位置參數(shù)傳遞進來.
關于可變參數(shù)呢?假如你想傳遞的參數(shù)是不確認多少個數(shù)量,比如參數(shù)是數(shù)組,可以在它前面帶有“*”:
sub sum(*@terms) {
[+] @terms
}
say sum 100, 20, 3; # 123
我使用同樣的例子來提出一個觀點:當你不提供任何符號到您的子程序時,你最終是得到的符號其實是是 *@_ .這是模擬 Perl 5 中的行為.
但數(shù)組前面的 * 號是僅用來捕獲的位置參數(shù)(positional arguments).如果你想捕捉命名參數(shù)(named arguments),你要使用 “slurpy hash”:
sub detect_nonfoos(:$foo!, *%nonfoos) {
say "Besides 'foo', you passed in ", %nonfoos.keys.fmt("'%s'", ', ');
}
detect_nonfoos(:foo(1), :bar(2), :baz(3));
# Besides 'foo', you passed in 'bar', 'baz'
哦,這可能是一個很好的通過以命名的參數(shù)傳遞哈希的方法,像這樣:
detect_nonfoos(foo => 1, bar => 2, baz => 3);
# Besides 'foo', you passed in 'bar', 'baz'
這里的 Perl 5 中的一個重要區(qū)別:默認參數(shù)是只讀的:
sub increase_by_one($n) {
++$n
}
my $value = 5;
increase_by_one($value); # boom
在這讓參數(shù)只讀,主要有兩個原因,其一為了效率.當變量只讀時可以使其最佳化,其二要鼓勵程序員寫程序時有個正確的習慣,只會有一點點不習慣.
所以這個功能不僅是為優(yōu)化好,更是為了讓你有個更好的靈魂.
下面是你需要做的工作:
sub increase_by_one($n is rw) {
++$n
}
my $value = 5;
say increase_by_one($value); # 6
有時可能你想讓你的這個參數(shù)可以讀寫(RW),但是有時你可能更想修改傳進來的參數(shù)復本.當你想使用這個 copy 時:
sub format_name($first, $middle is copy, $last) {
$middle .= substr(0, 1);
"$first $middle. $last"
}
原內容將保持不變.
在 Perl 6 中,當傳遞一個數(shù)組或哈希時,默認情況下它并不會給數(shù)組和哈希拉平成幾個參數(shù).相反,當你想讓參數(shù)扁平化時可以使用"|".
sub list_names($x, $y, $z) {
"$x, $y and $z"
}
my @ducklings = <huey dewey louie>;
try {
list_names(@ducklings);
}
say $!; # 'Not enough positional parameters passed;
# got 1 but expected 3'
say list_names(|@ducklings); # 'huey, dewey and louie'
同樣,如果扁平化一個哈希,其參數(shù)內容將作為命名的參數(shù)(named arguments)發(fā)送到函數(shù).
正如您傳送數(shù)組和哈希一樣,你也可以傳送代碼塊:
sub traverse_inorder(TreeNode $n, &action) {
traverse_inorder($n.left, &action) if $n.left;
action($n);
traverse_inorder($n.right, &action) if $n.right;
}
下面前三個印記符號(@ % & )其實是類型約束:
@ Array (actually, Positional)
% Hash (actually, Associative)
& Code (actually, Callable)
$ 的印記是工作在不受約束的版本.
當心!常出的簡單的小陷阱是人們常常落入指定類型約束兩次,還都是同一個類型:
sub f(Array @a) { ... } # WRONG, unless you mean Array of Array
sub f( @a) { ... } # probably what you meant
sub f(Int @a) { ... } # Array of Int
你學到這,你應得的另一個 Perl6 單行…
$ perl6 -e '.fmt("%b").trans("01" => " #").say for <734043054508967647390469416144647854399310>.comb(/.**7/)'
Going to the Rats
As I hinted at back in the in the Day 1 post, Perl 6 has rational numbers. They are created in the most straightforward fashion, by dividing an integer with another integer. But it can be a bit hard to see that there is anything unusual about the result:
> say (3/7).WHAT
Rat()
> say 3/7
0.428571428571429
When you convert a Rat to a Str (for example, to “say” it), it converts to a decimal representation. This is based on the principle of least surprise: people generally expect 1/4 to equal 0.25. But the precision of the Rat is exact, rather than the approximation you’d get from a floating point number like a Num:
> say (3/7).Num + (2/7).Num + (2/7).Num - 1;
-1.11022302462516e-16
> say 3/7 + 2/7 + 2/7 - 1
0
The most straightforward way to see what is going on inside the Rat is to use the .perl method. .perl is a standard Perl 6 method which returns a human-readable string which, when eval’d, recreates the original object as closely as is possible:
> say (3/7).perl
3/7
You can also pick at the components of the Rat:
> say (3/7).numerator
3
> say (3/7).denominator
7
> say (3/7).nude.perl
[3, 7]
All the standard numeric operators and operations work on Rats. The basic arithmetic operators will generate a result which is also a Rat if that is possible; the rest will generate Nums:
> my $a = 1/60000 + 1/60000; say $a.WHAT; say $a; say $a.perl
Rat()
3.33333333333333e-05
1/30000
> my $a = 1/60000 + 1/60001; say $a.WHAT; say $a; say $a.perl
Num()
3.33330555601851e-05
3.33330555601851e-05
> my $a = cos(1/60000); say $a.WHAT; say $a; say $a.perl
Num()
0.999999999861111
0.999999999861111
(Note that the 1/60000 + 1/60000 didn’t work in the last official release of Rakudo, but is fixed in the Rakudo github repository.)
There also is a nifty method on Num which creates a Rat within a given tolerance of the Num (default is 1e-6):
> say 3.14.Rat.perl
157/50
> say pi.Rat.perl
355/113
> say pi.Rat(1e-10).perl
312689/99532
One interesting development which has not made it into the main Rakudo build yet is decimal numbers in the source are now spec’d to be Rats. Luckily this is implemented in the ng branch, so it is possible to demo how it will work once it is in mainstream Rakudo:
> say 1.75.WHAT
Rat()
> say 1.75.perl
7/4
> say 1.752.perl
219/125
One last thing: in Rakudo, the Rat class is entirely implemented in Perl 6. The source code is thus a pretty good example of how to implement a numeric class in Perl 6.
.pick your game
December 15, 2009
又一個大學學期結束了,或者快要結束了,對于身在美國的大多數(shù)來說。這個禮物會有些樂趣,他可以 .pick 東西。
.pick 允許從一個列表中選擇隨機的元素,先來看看Perl5 的語法:
my @dice = (1, 2, 3, 4, 5, 6);
my $index = int (rand() * scalar @dice);
print $dice[$index] . "\n";
5
Perl 6 可以簡化這,同時能選擇多個元素.
my @dice = 1..6;
say @dice.pick(2).join(" ");
> 3 4
僅僅使用一套骰子,你就可以和你的朋友們進行角色扮演的會話了?,F(xiàn)在讓我們看看使用 10 次6面的骰子會有多少攻擊:
my @dice = 1..6;
say @dice.pick(10).join(" ");
> 5 3 1 4 2 6
對那些懷疑者,上面的結果并非拼寫錯誤。 .pick 的行為實際上和它的名字是一致的。當你把某個東西選出來,你通常不會把它放回去了。如果你想把它們再放回去,允許同一個項目被再次選中,請在第二個參數(shù)中使用副詞 :repalce。
my @dice = 1..6;
say @dice.pick(10, :replace).join(" ");
> 4 1 5 6 4 3 3 5 1 1
Note to game masters: don’t invite me to your D&D games unless you need someone with terrible dice luck. ;)
There is no specific order the list items have to be in for .pick to work its magic. Take the values of monopoly money, for instance:
my @dice = <1 5 10 20 50 100 500>;
say @dice.pick(10, :replace).join(" ");
> 20 50 100 500 500 10 20 5 50 20
When dice aren’t available, a deck of cards is usually on hand. This version is very basic, but is meant to get ideas going.
use v6;
class Card
{
has $.rank;
has $.suit;
multi method Str()
{
return $.rank ~ $.suit;
}
}
my @deck;
for <A 2 3 4 5 6 7 8 9 T J Q K> -> $rank
{
for <? ? ? ?> -> $suit
{
@deck.push(Card.new(:$rank, :$suit));
}
}
# Shuffle the cards.
@deck .= pick(*);
say @deck.Str;
> Not outputting the results here.
What does the pick(*) do? Call that a sneak peak for another gift. For now, see if you can improve on the card code and make a deck class.
With that, I hope I have proven that Perl 6 is fun. It certainly gets a high mark from me. ?
Whatever
by Moritz
Whatever 在 Perl 6 中是一種類型,在它出現(xiàn)的上下文中,Whatever 代表著它知道的任何東西。
例子:
1..* # infinite range
my @x = <a b c d e>;
say @x[*-2] # indexing from the back of the array
# returns 'd'
say @x.map: * ~ 'A'; # concatenate A to whatever the
# thing is we pass to it
say @x.pick(*) # randomly pick elements of @x
# until all are used up
say @array[*-5] 等價于:
say @array[-> $x { $x-5 }]; # $x 是數(shù)組元素的個數(shù)
my $make-index = -> $x { $x-5 };
say @array[$make-index];
所以這是怎么回事?
有些用法看起來很明顯: * 在 term 位置上會產生一個 Whatever 對象, 并且有些內置函數(shù)(例如 List.pick) 知道怎么處理這個 Whatever 對象。
編輯器讀取代碼后, 知道怎么解析項和操作符:
say 2 + 4
| | | |
| | | + term (literal number)
| | + operator (binary +)
| + term (literal number)
+ term (listop), which expects another term
所以,當你寫下:
* * 2
編譯器會把 第一個 * 解釋為 項, 把第二個 * 解釋為 操作符
上面那行代碼生成了一個代碼塊: * * 2 等價于 -> $x { $x * 2 }, 你可以想任何其它子例程或 block 一樣調用它:
my $x = * * 2;
say $x(4); # says 8
同樣地:
say @x.map: * ~ 'A';
等價于
say @x.map: -> $x { $x ~ 'A' };
而
say @x.map: *.succ;
等價于
say @x.map: -> $x { $x.succ };
Whatever 在排序時很有用 — 例如, 根據(jù)數(shù)字大小排序( 前綴 '+' 意味著獲取某個東西的數(shù)字值):
@list.sort: +*
等價于:
my $desc = -> $a, $b { $a <=> $b }
@list.sort: $desc
而把列表元素作為字符串排序 (前綴 '~' 意思是獲取某個東西的字符串值):
@list.sort: ~*
Junctions
December 13, 2009
Among the many exciting things in Perl 6, junctions are one of my favourites. While I know I don’t really comprehend everything you can do with them, there are a few useful tricks which I think most people will appreciate, and it is those which I’m going to cover as today’s gift.
Junctions are values which have (potentially) more than one value at once. That sounds odd, so let’s get thinking about some code which uses them. First, let’s take an example. Suppose you want to check a variable for a match against a set of numbers:
if $var == 3 || $var == 5 || $var == 7 { ... }
I’ve never liked that kind of testing, seeing as how it requires much repetition. With an any junction we can rewrite this test:
if $var == any(3, 5, 7) { ... }
How does this work? Right near the core of Perl 6 is a concept called junctive autothreading. What this means is that, most of the time, you can pass a junction to anything expecting a single value. The code will run for each member of the junction, and the result will be all those results combined in the same kind of junction which was originally passed.
In the sample above, the infix:<==> operator is run for each element of the junction to compare them with $var. The results of each test are combined into a new any junction, which is then evaluated in Boolean context by the if statement. An any junction in Boolean context is true if any of its values are true, so if $var matches any value in the junction, the test will pass.
This can save a lot of duplicated code, and looks quite elegant. There’s another way to write it, as any junctions can also be constructed using the infix:<|> operator:
if $var == 3|5|7 { ... }
What if you want to invert this kind of test? There’s another kind of junction that’s very helpful, and it’s called none:
if $var == none(3, 5, 7) { ... }
As you may have guessed, a none junction in Boolean context is true only if none of its elements are true.
Junctive autothreading also applies in other circumstances, such as:
my $j = any(1, 2, 3);
my $k = $j + 2;
What will this do? By analogy to the first example, you can probably guess that $k will end up being any(3, 4, 5).
There is an important point to note in these examples. We’re talking about junctive autothreading, which should give you a hint. By the Perl 6 spec, the compiler is free to run these multiple operations on junctions in different threads so that they can execute in parallel. Much as with hyperoperators, you need to be aware that this could happen and avoid anything which would make a mess if run simultaneously.
The last thing I want to talk about is how junctions work with smartmatching. This is really just another instance of autothreading, but there are some other junction types which become particularly useful with smartmatching.
Say you have a text string, and you want to see if it matches all of a set of regexes:
$string ~~ /<first>/ & /<second>/ & /<third>/
Assuming, of course, you have defined regexes called first, secondand third. Rather like |, & is an infix operator which creates junctions, this time all junctions which are only true if all their members are true.
The great thing about junctions is that they have this behaviour without the routine you’re passing them to having to know about it, so you can pass junctions to almost any library or core function and expect this kind of behaviour (it is possible for a routine to deliberately notice junctions and treat them how it prefers rather than using the normal autothreading mechanism). So if you have a routine which takes a value to smartmatch something against, you can pass it a junction and get that flexibility in the smartmatch for free. We use this in the Perl 6 test suite, with functions like Test::Util::is_run, which runs some code in another interpreter and smartmatches against its output.
To finish off, here are some other useful things you can do with junctions. First, checking if $value is present in @list:
any(@list) == $value
Junction constructors can work quite happily with the elements of arrays, so this opens up many possibilities. Others include:
all(@list) > 0; # All members greater than zero?
all(@a) == any(@b); # All elements of @a present in @b?
Go experiment, and have fun!
Modules and Exporting
December 12, 2009
Today I’d like to talk about a fairly fundamental subject: libraries.
To write a library in Perl 6, we use the “module” keyword:
module Fancy::Utilities {
sub lolgreet($who) {
say "O HAI " ~ uc $who;
}
}
Put that in Fancy/Utilities.pm somewhere in $PERL6LIB and we can use it like the following:
use Fancy::Utilities;
Fancy::Utilities::lolgreet('Tene');
That’s hardly ideal. Just like in Perl 5, we can indicate that some symbols from the module should be made available in the lexical scope of the code loading the module. We’ve got a rather different syntax for it, though:
# Utilities.pm
module Fancy::Utilities {
sub lolgreet($who) is export {
say "O HAI " ~ uc $who;
}
}
# foo.pl
use Fancy::Utilities;
lolgreet('Jnthn');
If you don’t specify further, symbols marked “is export” are exported by default. We can also choose to label symbols as being exported as part of a different named group:
module Fancy::Utilities {
sub lolgreet($who) is export(:lolcat, :greet) {
say "O HAI " ~ uc $who;
}
sub nicegreet($who) is export(:greet, :DEFAULT) {
say "Good morning, $who!"; # Always morning?
}
sub shortgreet is export(:greet) {
say "Hi!";
}
sub lolrequest($item) is export(:lolcat) {
say "I CAN HAZ A {uc $item}?";
}
}
Those tags can be referenced in the code loading this module to choose which symbols to import:
use Fancy::Utilities; # Just get the DEFAULTs
use Fancy::Utilities :greet, :lolcat;
use Fancy::Utilities :ALL; # Everything marked is export
Multi subs are export by default, so you only need to label them if you want to change that.
multi sub greet(Str $who) { say "Good morning, $who!" }
multi sub greet() { say "Hi!" }
multi sub greet(Lolcat $who) { say "O HAI " ~ $who.name }
Classes are just a specialization of modules, so you can export things from them as well. In addition, you can export a method to make it available as a multi sub. For example, the setting exports the “close” method from the IO class so you can call “close($fh);”
class IO {
...
method close() is export {
...
}
...
}
Perl 6 does support importing symbols by name from a library, but Rakudo does not yet implement it.
Roles
by jnthnwrthngtn
As the snow falls outside, we grab a glass of mulled wine – or maybe a cup of eggnog – to enjoy as we explore today’s exciting gift – roles!
Traditionally in object oriented programming, classes have taken on two tasks: instance management and re-use. Unfortunately, this can end up pulling classes in two directions: re-use wants them to be small and minimal, but if they’re representing a complex entity then they need to support all of the bits it needs. In Perl 6, classes retain the task of instance management. Re-use falls to roles.
So what does a role look like? Imagine that we are building up a bunch of classes that represent different types of product. Some of them will have various bits of data and functionality in common. For example, we may have a BatteryPower role.
role BatteryPower {
has $.battery-type;
has $.batteries-included;
method find-power-accessories() {
return ProductSearch::find($.battery-type);
}
}
At first glance, this looks a lot like a class: it has attributes and methods. However, we can not use a role on its own. Instead, we must compose it into a class, using the does keyword.
class ElectricCar does BatteryPower {
has $.manufacturer;
has $.model;
}
Composition takes the attributes and methods – including generated accessors – from the role and copies them into the class. From that point on, it is as if the attributes and methods had been declared in the class itself. Unlike with inheritance, where the parents are looked at during method dispatch, with roles there is no runtime link beyond the class knowing to say “yes” if asked if it does a particular role.
Where things get really interesting is when we start to compose multiple roles into the class. Suppose that we have another role, SocketPower.
role SocketPower {
has $.adapter-type;
has $.min-voltage;
has $.max-voltage;
method find-power-accessories() {
return ProductSearch::find($.adapter-type);
}
}
Our laptop computer can be plugged in to the socket or battery powered, so we decide to compose in both roles.
class Laptop does BatteryPower does SocketPower {
}
We try to run this and…BOOM! Compile time fail! Unlike with inheritance and mix-ins, role composition puts all of the roles on a level playing field. If both provide a method of the same name – in this case, find-power-accessories – then the conflict will be detected as the class is being formed and you will be asked to resolve it. This can be done by supplying a method in our class that says what should be done.
class Laptop does BatteryPower does SocketPower {
method find-power-accessories() {
my $ss = $.adapter-type ~ ' OR ' ~ $.battery-type;
return ProductSearch::find($ss);
}
}
This is perhaps the most typical use of roles, but not the only one. Roles can also be taken and mixed in to an object (on a per-object basis, not a per-class basis) using the does and but operators, and if filled only with stub methods will act like interfaces in Java and C#. I won’t talk any more about those in this post, though: instead, I want to show you how roles are also Perl 6’s way of achieving generic programming, or parametric polymorphism.
Roles can also take parameters, which may be types or just values. For example, we may have a role that we apply to products that need to having a delivery cost calculated. However, we want to be able to provide alternative shipping calculation models, so we take a class that can handle the delivery calculation as a parameter to the role.
role DeliveryCalculation[::Calculator] {
has $.mass;
has $.dimensions;
method calculate($destination) {
my $calc = Calculator.new(
:$!mass,
:$!dimensions
);
return $calc.delivery-to($destination);
}
}
Here, the ::Calculator in the square brackets after the role name indicates that we want to capture a type object and associate it with the name Calculator within the body of the role. We can then use that type object to call .new on it. Supposing we had written classes that did shipping calculations, such as ByDimension and ByMass, we could then write:
class Furniture does DeliveryCalculation[ByDimension] {
}
class HeavyWater does DeliveryCalculation[ByMass] {
}
In fact, when you declare a role with parameters, what goes in the square brackets is just a signature, and when you use a role what goes in the square brackets is just an argument list. Therefore you have the full power of Perl 6 signatures at your disposal. On top of that, roles are “multi” by default, so you can declare multiple roles with the same short name, but taking different types or numbers of parameters.
As well as being able to parametrize roles using the square bracket syntax, it is also possible to use the of keyword if each role takes just one parameter. Therefore, with these declarations:
role Cup[::Contents] { }
role Glass[::Contents] { }
class EggNog { }
class MulledWine { }
We may now write the following:
my Cup of EggNog $mug = get_eggnog();
my Glass of MulledWine $glass = get_wine();
You can even stack these up.
role Tray[::ItemType] { }
my Tray of Glass of MulledWine $valuable;
The last of these is just a more readable way of saying Tray[Glass[MulledWine]]. Cheers!
About these ads
Like this:
Like Loading...