2009
有用的和有意思的循环
让我们来看一个基本的例子.
for 1, 2, 3, 4 { .say }
这是一个最简单清晰的语法的例子.在这并没有使用括号
来包起整个列表的语句,象这种写法可以贯穿整个 Perl 6. 通常比起 Perl 5 来你没有必要写那么多的括号了.
很象 Perl 5 , 这个循环中的值会默认存到 $_
.在这个方法调用的 say 其实就是 $_.say
.注意在 Perl 6 中,你不能直接只打一个 say 而不加参数,它会默认使用 $_ 来传参.你需要使用 .say
。要么明确的指定是 $_
.
下面这个语法块并不是一个普通的块.它能通过一个尖的指向,来告诉你的 循环变量传进去的参数的名字 .
for 1, 2, 3, 4 -> $i { $i .say }
如果你调用的 return 内部有这个,将返回闭合的子函数.
这个尖尖也能取 多个 参数.象下面这样.
1
2
3
for 1, 2, 3, 4 -> $i , $j { "$i, $j" .say }
# 1 2
# 3 4
实际做了些什么啦?就是你在列表进行迭代时一次取了两个
元素 . 如果你不明确指明参数的话,就退化到 Perl 5 一样使用 $_
.
我们可以意识到这个我们能做什么,比如迭代一个列表。当然,也可以是一个数组的值.
for @array { .say }
这是一个非常简单的例子,我们可能更加喜欢使用 map:
@array.map : *.say;
如果对你来讲 顺序 和连续的并不重要,你可以使用 hyperoperator(超运算符)
,上一个文章中也讲过这个,今天的主题也不详细讲这个了.
@array».say;
我们也能使用 范围构造器中缀操作符 ..
来生成一个数字的列表:
for 1..4 { .say }
有一个最通用的功能,就是我们想些生成一个从 0
开始到 $n
的数字的列表,比如常用的数组下标.我们可以写成 0 .. $n-1
或者另一个不同的范围构造器 0..^$n
.但在 Perl 6 中提供了一个短的快捷的方法就是使用前缀的 ^
.
for ^4 { .say }
0
1
2
3
一个常用的理由是,人们在 Perl 5 中常常退回到 C 风格的循环的原因是必须知道 for 的成员数组中索引的位置,或者因为必须并行的迭代二个和更多的数组.Perl 6 提供了一个短的快捷方法,就是中缀的 Z 这个 zip 操作符.
for @array1 Z @array2 -> $one , $two { ... }
假设二个数组是相同的长度$one
会是第一个 @array1
的成员元素,$two
会是相应的位置 @array2
的成员元素.如果是不同的长度的话.迭代会停止到短的那个数组结束的长度.
我们可以很容易地在迭代数组包含进索引:
for ^Inf Z @array -> $index , $item { ... }
如果一个无限长的列表,会让你害怕使用上面用法的话,可以象下面这样,使用前缀操作符 ^
来取出数组元素的长度.
for ^@array.elems Z @array -> $index , $item { ... }
上面这个可以得到相同的结果,但是更加优雅.因为中缀操作符 Z
操作时,第一个元素的长度决定了什么整个长度.
for @array.kv -> $index , $item { ... }
@array.kv
会返回 keys
和 values
的交错,这个 $key
是数组元素的下标.所以同时迭代这二个可能是你比较想要的效果.
希望这篇文章让你了解 Perl 6 灵活的循环相关的一些概念,它们可以灵活的使用在各种常见任务上.在这之前,我要回答最后一个问题,我知道有人一直在想这个问题.怎么样一次性迭代四个数组.
for @one Z @two Z @three Z @four -> $one , $two , $three , $four { ... }
这是一个关联列表中缀操作符,这样使用,是不是一种享受?
超运算符
pmichaud 在昨天介绍了 Perl 6 的 hyper 运算符,我这要进一步来探索 Perl6 中强大的元操作的特性.
首先,为简单起见,我将编写一个 lsay 的函数,可以轻松地得到好看的列表值的输出.这个 sub 是用我们用 Perl 来创建的
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
如果数组的长度是相同的,上面这两种形式之间没有区别.但是,如果长度是不同的:
> (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
这规则是, 象诸如此类的尖尖是用来表明 hyperoperator 使用时,当一端比另一端短,可以延长短的那一端来进行扩展延伸.
象如果是尖尖指向内部,是指不能进行扩展延伸.当然,还可以有各种组合都是可以的.所以你也能指出只有左边
能扩展延伸 (<<+<<
),也可以只指出只有右边
能(>>+>>
).当然也能二边都是可以扩展延伸 (<<+>>
),或者二边都不能扩展延伸 (>>+<<
). R 语言中也有向量的循环法则。
单标量扩展延伸如下:
> (1, 2, 3, 4) >>+>> 2
3, 4, 5, 6
> 3 <<+<< (1, 2, 3, 4)
4, 5, 6, 7
因此,这就是基本的使用中缀操作符 hyperoperator 的方法.您还可以使用前缀和后缀运算符:
单边运算时,元素要能漏到操作符的左边( 如@a>>++
)或右边( 如 ~<<
)。想象一下漏斗,总是从大的口向小的口漏。
所以操作符前面或后面接什么样的超运算符要取决于操作数是在操作符的前面(用>>
)或后面(用<<
)
> ~<<(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 并不只是只能和内置操作符一起工作.他们也将能跟你定义以及任何新的运算符工作的很好(即大多数的的都能正常在现在的 Rakudo 上工作).
只要给放在适当的地方.如@a >>/=>>2
整个数组成员都除以 2. 他们将来能和更多的结构一起工作,如多维列表,树与哈希;我们可 S03 Hyper operators .(据我所知,有些功能还尚未在 Rakudo 正常实现)
我并不知道是否有很多代码示例中广泛使用 hyperoperators. 但 LastOfTheCarelessMen’s Vector 是一个非常好的实现.它使用单循环直接的实现了一个 N 维向量类.
reduce 和 hyper 元操作符
Hyper 亢奋的;精力旺盛的 Hyper[ˈhaɪpə(r)]
今天是第四天,在这个小盒子中,你会见到一些有意思的实现阶乘的函数
sub fac( Int $n ) {
[*] 1.. $n
}
Okay, 它是怎么工作的? 今天的 Advent 的盒子就是为了给你提供答案.
Perl 6 有一些不同的”元操作符”是用来修改现有的运算符完成更加强大的功能.
这个方括号中是一个有关“reduce metaoperator”的元操作符的例子,它是中缀运算符,会变成列表操作,操作是在后面各个元素的中间来, 例如,表达式
[+] 1, $a , 5, $b
它相当于
1 + $a + 5 + $b
这为我们提供了非常便利的机制“计算整个列表中的所有元素之和”:
$sum = [+] @a ; # @a 中所有元素之和
更多的中缀运算符(包含用户自己定义的),都能放到这个方括号来减少操作符;
$prod = [*] @a ; # 相乘 @a 中所有的元素
$mean = ([+] @a ) / @a ; # 计算 @a 的平均值
$sorted = [<=] @a ; # 如果 @a 元素是数字排序就为 true
$min = [min] @a , @b ; # find the smallest element of @a and @b combined
在那个阶乘的子函数中,表达示 [*] 1..$n
返回全部 1
到 $n
之间所有乘数的乘积.
另一个非常有用的元操作符是 “hyper” 操作符,放置 >>
(与|
或)<<
在操作符的二边(一边),使得那个操作 “hyper”(更亢奋).这个是用来操作列表中所有的成员,来进行这个包起来的运算符的操作.象下面的例子,我们来打算从 @a 和 @b 中成对的取出数据来进行运算后存入 @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 ;
我们还可以翻转<<的角度
,使标量的行为像一个数组
:
# @a 中每个成员都乘 3.5
my @a=2,4,6;
@b = @a >>*>> 3.5;
这其实相当于 @b = @a >>*<< (3.5,3.5,3.5)
较短的向量会被自动循环使用!模仿 R 语言的短向量自动循环。
如果右边的向量没有左边的长,箭头就指向那个单个向量。
# @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
>>~<<
两侧的元素个数必须相同!
当然,reductions 和 hyper 操作符也能联合表达式
# 计算 @x 的平方和
$sumsq = [+] ( @x >>**>> 2);
还有很多其他元操作符,包括X(cross交叉),R(reverse反向),S(顺序sequential).事实上,这只是在恰当的位置放个运算符,如+=,*=,?=
,只是元形式的后缀等号运算,它相当于:
$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
的标量只能包含二种东西引用或值,这值可以是任何东西,能是整数,字符,数字,日期和你的名字.这通常是非常方便的,但并不明确.
在 Perl6 中给你机会修改标量的类型 .如果你这个值比较特别,你可以直接放个类型名在 my
和 $variable
的中间.象下面的例子,是在设置値一定要是一个 Int 型的数据,来节约 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
可以做更多其它的事情,所以下次其它作者的礼物中也会有这个,但现在我们会先介绍几个非常有用的一些 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);
还有两个礼物很有优势吧.你可以尝试多使用他们,我们会不断的丰富这个 Advent 的树,并不断放更多的礼物,希望你能多来看看.
.comb your constraints
我们以前 advent 了解过的内容,对于今天所要介绍的礼物非常有用,今天要讲两个东西: comb 方法和 constraints 的概念。
constraints 和原来那章节中提到的静态变量
定义的相同,constraints 可以让我们在写程序的时候就更方便的在子函数和方法上进行控制.
在很多其它的程序中,你可以通过参数调用子函数并可以在参数进入的时候就通过 constraints
来验证输入的内容.这样我们就能在程序声明的时候就验证输入的内容,不在需要等到程序运行的时候.
下面是一个基本的例子,如果是一个整数和偶数,在子函数中它会不能处理下去.在 Perl 5 中的实际基本就象下面这样子了:
sub very_odd
{
my $odd = shift;
unless ($odd % 2)
{
return undef;
}
# 在这接着处理奇数.
}
在 Perl 6 中,我们可以只需要简单的:
sub very_odd(Int $odd where {$odd % 2})
{
# 在这接着处理奇数.
}
如果你试图来传入一个偶数来调用 very_odd.你会直接得到一个 error.不要担心:你可以使用 multi sub 的功能来给偶数一个机会:
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
? 当我们早上梳整我们的头发时,我们先通常使用梳子来梳成你想要的样子(线条),然后在你的头上固定梳成的样子.前面讲的内容在这非常象.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
. 它会取得每个字母然后放到一个暂时的数组中,然后使用 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
**
在正则里是量词,表示重复前面的十六进制数两次,合起来就是每两个字符分一下。
如果你提心这个,你可以使用 .map
的方法:
say "5065726C36".comb(/<xdigit>**2/).map: { chr '0x' ~ $_ } ;
#Outputs "Perl6"
记的,这是 Perl.做任何事情都不只一种方法.
今天给完了所有礼物,我现在向你挑战.有 KyleHasselbacher 的协助,我们能使用约束.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 中和今天的礼物中的学到的内容来编写编码算法.毕竟,编程语言本身只有更多的使用,才能让它变的更优秀.
一个正则表达式的故事
By perlpilot
在 advent 的第十天,我们有一个故事做为礼物……
曾几何时,在比你想象的更近的时候,一个叫 Tim 的学 Perl 6 程序的学生,工作中出现了一个简单的解析相关的问题.他的老板(我们叫他 C 先生)曾问过他,解析日志文件中包含着库存信息,确保在文件内是唯一有效的行.文件中每行内是这样的:
<part number> <quantity> <color> <description>
所以这个 Perl 6 的学生,他用熟悉正则表达式写了一个可爱的小正则表达式,可以用来找出有效的行.代码检查每行内容是这样写的:
next unless $line ~~ / ^^ \d+ \s+ \d+ \s+ \S+ \s+ \N* $$ /
使用 ~~
操作符的原因是因为,右侧的正则表达式会匹配左侧标量.在正则内部,^^
是匹配行的开头,\d+
是用来匹配一个或者多个数字(由零件编号 part number 和数量 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 先生都很高兴.
但随后发现了一些行中,没有从描述信息中给颜色信息分开,这些行其实也是有效的.在行中颜色信息和描述信息有个特殊的组合方式.他们总是象下面这样:
<part number> <quantity> <description> (<color>)
在这像以前一样,可以加入包括任意数量的空格在字符中. Tim 认为,“现在这个本来简单的解析程序似乎突然更加复杂了”.幸运的是,Tim 可以找一个地方寻求帮助.他迅速登录到 irc.freenode.org
,加入 #perl6
通道 并请求大家协助.有人建议他使用名字来命名他的正则表达式的各个部分,来使事情变得更容易.然后使用交替的方法来匹配这个正则表达式的最后一部分的多种可能.
首先,Tim 尝试给正则能捕获到的每个部分都加上一个名字,详细信息可以见 Perl 6 正则的纲要,下面是他所做的:
next unless $line ~~
/ ^^ $<product>=(\d+) \s+ $<quantity>=(\d+) \s+ $<color>=(\S+) \s+ $<description>=(\N*) $$ /
现在,成功的匹配后,每各部分都可以匹配到对象中不同的东西,通过特殊的变量 $<Product>
,$<quantity>
,$<color>
和 $<description>
.
这比预期的更容易,让 Tim 感到非常有信心.接着,他需要补充:交替区分两种不同的有效行:
next unless $line ~~ / ^^
$<product>=(\d+) \s+ $<quantity>=(\d+) \s+
[
| $<description>=(\N*) \s+ '(' $<color>=(\S+) ')'
| $<color>=(\S+) \s+ $<description>=(\N*)
]
$$
/
为了从正则表达式中的交替和其余部分隔离开,Tim 使用了分组括号([ and ]
)在要交替检查的部分.
这个分组是正则的一部分,其中像圆括号是唯一没有捕捉到 $0
的, 由于必须匹配到精确的圆括号, Tim 使用了另一个有用的 Perl6 正则表达式的优势:带引号的字符串字面匹配.因为分配给正则表达式的中 $<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 正则表达式可以被复用.在匹配单一的词时,复用正则表达式是很荒谬的;但在解析配置文件时,几乎必须要复用正则表达式(这取决于配置文法的复杂度、发生修改的频率等).这样性能也会高很多.
在 Perl 5 中, Regexp::Common 模块,已经在尝试复用正则表达式,但是,因为 Perl 5 不允许复用正则表达式,所以不得不将它们封装在一个模块接口中. Perl 6 完全支持这种复用.
其它参数资料:
类, 属性, 方法和其它
By jnthnwrthngtn
我非常兴奋地撕下今天的礼物上闪亮的包装纸,里面是无可争议的 Perl 6 的对象模型,它内置了其类声明,角色组成,自豪的元模型(meta-model).除了有先进的功能外,让我们看看在 Perl 6 中是多么容易写一个类.
class Dog {
has $.name;
method bark($times) {
say "w00f! " x $times;
}
}
我们开始使用一个 class 的关键字.如果你有学过 Perl5 的话,你能想到的类有点像包(package)的变种,这个关键字为您提供一个优雅的语义.
接下来,我们使用 has 的关键字来声明属性访问器方法.这个”.”的东西名叫 twigil. Twigil 是用来告诉你指定变量的作用域.它是”属性 + 存取方法”的组合.它的选项是:
has $!name; # 私有; 只能在内部可见
has $.name is rw; # Generates an l-value accessor
接下来是方法的使用,并介绍使用 method 的关键字.在对象中的方法象包中的子函数,不同之处在于方法是放在类的方法列表的条目中.
它还能自动取得调用者(invocant),所以你如果没有在参数列表中加入参数.它是会给自我传递过去.在 Perl 5 中需要我们显示的写 $self = shift
.
所有的类都继承一个叫 new 的默认的构造器,会自动的映射命名参数到属性,所有传进的参数会存到属性中.我们可以调用 Dog 的构造器(这个 Dog 的类的对象,并取得一个新的实例).
my $fido = Dog.new(name => 'Fido');
say $fido.name; # Fido
$fido.bark(3); # w00f! w00f! w00f!
请注意,Perl 6 中的方法调用操作符是”.”而不是 Perl 5 中使用的”->”.它缩短了 50% 并更加合适从其他语言转过来的开发人员.
当然,很容易实现继承,下面我们建一个叫 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 的方法.重命名只是其中的一个可选方式;委托常常有很多其它的实现方法.
内心深层之美比外在更加重要.所以,在整洁的语法之下是使用 meta-model(元模型)想法来实现对象.类,属性和方法都是 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 中,不仅为您提供了强大的对象模型,但也提供了机会,用来实现其它的特性,以满足未来我们还没有想到的需求.
这些都只是 Perl 6 的对象模型所提供的伟大的事情中的一些,也许我们会发现更多的东西在其他礼品中. :-)
注:
面向对象的概念
首先,我们定义几个预备性的术语.
构造器 (constructor): 创建一个对象的函数.
实例 (instance): 一个对象的实例化实现.
标识 (identity): 每个对象的实例都需要一个可以唯一标识这个实例的标记.
实例属性 (instance attribute): 一个对象就是一组属性的集合.
实例方法 (instance method): 所有存取或者更新对象某个实例一条或者多条属性的函数的集合.
类属性(class attribute): 属于一个类中所有对象的属性,不会只在某个实例上发生变化.
类方法(class method): 那些无须特定的对性实例就能够工作的从属于类的函数.
委托 (Delegation): 在对象需要执行某个工作时并不直接处理,而是要求另一个对外象代为处理(有时只处理部分工作),所以这时第二个对象代表第一个对象来执行该操作。
调用者(invocant): 对类来讲,调用者是包的名字,对实例方法来讲,调用者是指定对象的引用.换句话讲,调用者就是调用方法的那种东西,有的文章叫他为代理(agent)施动者(actor).
抽象类(abstract class):抽象类实现类的占位符,主要用来定义行为,而子类用来实现这个行为。
arguments and parameters
By carl
在第9天的 advent 中…我打开了 …这是有关 parameters 和 arguments
你也许了解或者不了解 Perl5 的 是怎么处理函数参数的.先让你看看,它通常象下面的这个例子这样:
sub sum {
[+] @_
}
say sum 100, 20, 3; # 123
这个 [+] 是在 Perl 6 中的,但我们也可以写成 Perl 5 风格的
my $i = 0;
$i _= $_ for @_;
$i;
我们要想到上面这些区别,这些在 Perl 6 中非常重要,也就是为什么我们讲 Perl 6 比 Perl 5 好.当你调用函数时.你可以从 @_ 找到你的参数.你然后取出它们来做一些操作.
这是非常灵活的.因为它不会对参数做任何默认的处理,程序会全部传给你来进行处理.当然这也同样是令人厌烦因为样样都要自己处理,但很方便我们来进行扩展进行参数的检查,看下面这个虚构的例子.
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 中工作)
现在,这一刻,看看上面的内容,看到手册中的参数验证的实现,你是不是开始有点绝望吗?你感觉到了吧?好.
在 Perl 5 中的解决方法是使用优秀的 CPAN 模块,象 Sub::Signatures
和 MooseX::Declare
,然后在你的程序中使用这些模块,并按照模块设置就行了.
在 Perl 6 的中的解决方法是,给你参数设置默认范围. 我在想看了下面这些时, “请确保键盘前的你不会流口水”.在 Perl 6 中,我会写这样来写子函数:
sub grade_essay(Essay $essay, Int $grade where 0..5) {
%grades{$essay} = $grade;
}
现在我们见到,在这程序运行会对这个长版本的参数进行检查,没有必要在导入其它的 CPAN 的模块了.
有时,我们可以提供一些默认的值给参数:
sub entreat($message = 'Pretty please, with sugar on top!', $times = 1) {
say $message for ^$times;
}
如果这些参数的默认的值是不固定的,可以使用老的方式来传参数.
sub xml_tag ($tag, $endtag = matching_tag($tag) ) {...}
如果您的参数是不确定的,对这种可选的参数可以加一个 ? 的标记.
sub deactivate(PowerPlant $plant, Str $comment?) {
$plant.initiate_shutdown_sequence();
say $comment if $comment;
}
有一个特性,我特别喜欢,我们可以在调用时通过参数名字来引用参数,这样你可以以喜欢的任何顺序传递命名参数.这样会永远记得在这个函数中参数本来的顺序:
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! :-/
这的方法是引用参数的名字,来使得这个问题被解决:
draw_line(:x1($x1), :y1($y1), :x2($x2), :y2($y2)); # works
draw_line(:x1($x1), :x2($x2), :y1($y1), :y2($y2)); # also works!
冒号的意思是 “这来自命名参数”, 整个结构读作:name_of_parameter($variable_passed_in).这可以使用的参数和变量具有相同的名称,但有一个简短形式:
draw_line(:$x1, :$y1, :$x2, :$y2); # works
draw_line(:$x1, :$x2, :$y1, :$y2); # also works!
我喜欢短形式.我觉得它使我的代码更具可读性.
如果作为 API 的作者,要强迫别人使用命名参数 – 例如还是在 draw_line 的情况下 – 你只需要提供在子程序参数前的冒号.
sub draw_line(:$x1, :$y1, :$x2, :$y2 ) { ... } # optional nameds
但要小心注意,命名参数默认是可选的.换句话说,上述内容相当于:
sub draw_line(:$x1?, :$y1?, :$x2?, :$y2?) { ... } # optional nameds
如果你想明确地指出必需的参数,可以追加!对下面的这些参数:
sub draw_line(:$x1!, :$y1!, :$x2!, :$y2!) { ... } # required nameds
现在调用这个,就像他们是普通的顺序位置参数传递进来.
关于可变参数呢?假如你想传递的参数是不确认多少个数量,比如参数是数组,可以在它前面带有“*”:
sub sum(*@terms) {
[+] @terms
}
say sum 100, 20, 3; # 123
我使用同样的例子来提出一个观点:当你不提供任何符号到您的子程序时,你最终是得到的符号其实是是 *@_
.这是模拟 Perl 5 中的行为.
但数组前面的 * 号是仅用来捕获的位置参数(positional arguments).如果你想捕捉命名参数(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'
哦,这可能是一个很好的通过以命名的参数传递哈希的方法,像这样:
detect_nonfoos(foo => 1, bar => 2, baz => 3);
# Besides 'foo', you passed in 'bar', 'baz'
这里的 Perl 5 中的一个重要区别:默认参数是只读的:
sub increase_by_one($n) {
++$n
}
my $value = 5;
increase_by_one($value); # boom
在这让参数只读,主要有两个原因,其一为了效率.当变量只读时可以使其最佳化,其二要鼓励程序员写程序时有个正确的习惯,只会有一点点不习惯.
所以这个功能不仅是为优化好,更是为了让你有个更好的灵魂.
下面是你需要做的工作:
sub increase_by_one($n is rw) {
++$n
}
my $value = 5;
say increase_by_one($value); # 6
有时可能你想让你的这个参数可以读写(RW),但是有时你可能更想修改传进来的参数复本.当你想使用这个 copy 时:
sub format_name($first, $middle is copy, $last) {
$middle .= substr(0, 1);
"$first $middle. $last"
}
原内容将保持不变.
在 Perl 6 中,当传递一个数组或哈希时,默认情况下它并不会给数组和哈希拉平成几个参数.相反,当你想让参数扁平化时可以使用”|”.
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'
同样,如果扁平化一个哈希,其参数内容将作为命名的参数(named arguments)发送到函数.
正如您传送数组和哈希一样,你也可以传送代码块:
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
又一个大学学期结束了,或者快要结束了,对于身在美国的大多数来说。这个礼物会有些乐趣,他可以 .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
仅仅使用一套骰子,你就可以和你的朋友们进行角色扮演的会话了。现在让我们看看使用 10 次6面的骰子会有多少攻击:
my @dice = 1..6;
say @dice.pick(10).join(" ");
> 5 3 1 4 2 6
对那些怀疑者,上面的结果并非拼写错误。 .pick
的行为实际上和它的名字是一致的。当你把某个东西选出来,你通常不会把它放回去了。如果你想把它们再放回去,允许同一个项目被再次选中,请在第二个参数中使用副词 :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 中是一种类型,在它出现的上下文中,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 是数组元素的个数
my $make-index = -> $x { $x-5 };
say @array[$make-index];
所以这是怎么回事?
有些用法看起来很明显: *
在 term
位置上会产生一个 Whatever
对象, 并且有些内置函数(例如 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 在排序时很有用 — 例如, 根据数字大小排序( 前缀 ‘+’ 意味着获取某个东西的数字值):
@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…