免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: iakuf

[Perl 6]Perl 5 to 6 中译版 ( 完整 ) [复制链接]

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-16 10:02 |显示全部楼层
"Perl 5 to 6" Lesson 12 - Laziness

概要
  1. my @integers = 0..*;
  2. for @integers -> $i {
  3.     say $i;
  4.     last if $i % 17 == 0;
  5. }

  6. my @even := map { 2 * $_ }, 0..*;
  7. my @stuff := gather {
  8.     for 0 .. Inf {
  9.         take 2 ** $_;
  10.     }
  11. }
复制代码
描述

对于象列表之类, 在 Perl 6 中都是 lazy 的.

之所以叫 lazy 意味着, 这个东西的生成都是尽可能延迟. 当你写象 @a := map BLOCK, @b, 这个块并不执行. 当有当你从 @a 中访问这个东西的时候, 这个 map 函数才开始根据需要一个个执行和填充所需要的数据.

注意这个地方, 是使用的绑定操作符 (:=), 而不是分配: 如果直接声明分配的函数会立即创建内容 ( 除非编译器知道这个列表是无限长的, 才不会生成 ), 而使用绑定并不会立即生成内容.

Laziness 的特性可以让你处理无限长度的列表: 他们所占的空间就和你操作这个列表的长度一样大. 所以你没一个个操作完元素之前, 这些并不会生成.

但这存在一些缺陷, 比如列表的排序会失去 Laziness 的特性. 如果这个列表是无限的, 有可能容易出现无限循环.

在正常的情况下, 如果转换成标量 ( 象 List.join ) 也会不使用 Laziness 的特性.

Laziness 的特性可以防止不必要的计算, 所以可以提升性能, 同时保持代码的简单.

当你在 Perl 5 中一行行读文件时, 你之所以不使用 for (<HANDLE>) 是因为会读所有的文件到内存中, 然后才开始迭代, laziness 在这处理是不同的:
  1. my $file = open '/etc/passwd';
  2. for $file.lines -> $line {
  3.     say $line;
  4. }
复制代码
由于 $file.lines 在 Perl 6 中本身就是 lazy list, 所以只有物理读到文件时, 才会取到内存中来 ( 当然除了 buffer 的需要外 ).
gather/take

创建 lazy lists 一个非常有用的方式是使用 gather { take }. 它象下面这样使用:
  1. my @list := gather {
  2.     while True {
  3.         # some computations;
  4.         take $result;
  5.     }
  6. }
复制代码
这个 gather BLOCK 返回一个 lazy list. 只有当有操作必须使用 @list 中的元素时, 这个代码块才会运行直 take 块执行. 这个 take 就象 return 函数. 全部的 take 的返回的内容会构建 @list. 当需要更加的@list 块的元素时, 又会接着从上次的 take 之后执行这个代码块.

gather/take 是动态的范围, 所以它可以调用 take 在 gather 块的词法范围之外.
  1. my @list = gather {
  2.     for 1..10 {
  3.         do_some_computation($_);
  4.     }
  5. }

  6. sub do_some_computation($x) {
  7.     take $x * ($x + 1);
  8. }
复制代码
注意这个 gather 也可以是单个语句声明, 而不是一个块:
  1. my @list = gather for 1..10 {
  2.     do_some_computation($_);
  3. }
复制代码
控制 Laziness

Laziness 特性有可能会产生其它副作用 ( 当你尝试和学习过 Haskell 时, 你会发现它们中的 IO 系统是有点奇怪. 因为他们是使用的 lazy 特性, 并且没发现什么副作用 ), 当你不想使用这个特性的时候, 可以在前面加个 eager 的关键字.
  1. my @list = eager map { $block_with_side_effects }, @list;
复制代码
默认的情况下, lazy 只在列表的时候生效. 但你也可以让 lazy 工作在标量上:
  1. my $ls = lazy { $expansive_computation };
复制代码
动机

在计算机科学中, 大多数的问题都可以通过定义树来解决现实中的问题, 常用的一个方案是进行高效的算法和有效的搜索, 并且, 构建树其实也是非常有趣的部分.

通过 lazy 的列表, 你可以递归定义这个树并且查找它, 这个会特性会自动构造只有你实际使用过的部分.

在一般的情况下, laziness 特性可以让你写程序更加容易, 因为这是透明的. 就算计算的所有结果都会被使用到, 你也不会失去什么. 如果他不使用, 就根本也不会执行.

另请参阅

http://perlcabal.org/syn/S02.html#Lists

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-16 10:06 |显示全部楼层
本帖最后由 iakuf 于 2014-05-16 10:19 编辑

"Perl 5 to 6" Lesson 13 - 定制操作符

概要
  1. multi sub postfix:<!>(Int $x) {
  2.     my $factorial = 1;
  3.     $factorial *= $_ for 2..$x;
  4.     return $factorial;
  5. }

  6. say 5!;                     # 120
复制代码
描述

操作符其实是一个有着特别的名字的函数, 并且有一些额外的属性, 象优先级和结合性. 在 Perl 6 中通常的操作符的模式是 term infix term ( 词语 中缀 词语 ), 词语能选择是前面使用前缀运算符,还是使用后缀或者 postcircumfix 操作符.
  1. 1 + 1               infix         ( 中缀 )
  2. +1                  prefix        ( 前缀 )
  3. $x++                postfix       ( 后后缀 )
  4. <a b c>             circumfix     ( 环缀 )
  5. @a[1]               postcircumfix ( 后环缀 )
复制代码
操作符的名字并不限制使用特定的字符, 你可以包含使用任何东西, 除了空白.

上面后面这个运算符的长的名字是它的类型, 操作符用于跟着一个冒号和一个字符串文字或符号或符号的列表, 例如 infix:<+> 是 1+2 的运算. 其它的例子是 postcircumfix:<[ ]> 操作, 它是对 @a[0] 的操作.

当你知道这些知识, 你现在可以重新定义新运算符:

  1. multi sub prefix:<^> (Str $x) {
  2.     2 *  $x;
  3. }
  4. say ^4;                         # 8
复制代码
优先级

中缀表达式象 $a + $b * $c, 这中间的 infix:<*> 操作符优先级比 infix:<+> 高, 这也就是为什么可以计算 $a + ($b * $c).

新运算符的优先级可以通过下面的方式来指定:

  1. multi sub infix:<foo> is equiv(&infix:<+>) { ...  }
  2. mutli sub infix:<bar> is tighter(&infix:<+>) { ... }
  3. mutli sub infix:<baz> is looser(&infix:<+>) { ... }
复制代码
结合性

很多的中缀操作符只有二个参数. 象执行 1 / 2 / 4 中的操作符的结合性是根据结合性顺序来的. 这个 infix:</> 操作符是左结合性, 所以执行的解析是 (1 / 2) / 4 的顺序来的. 如果是右结合性操作符, 象 infix:<**> 在执行 2 ** 2 ** 4 时执行的解析顺序是 2 ** (2 ** 4).

Perl 6 中有着更加丰富的结合性: 如 none 是禁止优先级相同的操作符进行链接操作 (例如 2 <=> 3 <=> 4 是被禁止的 ), infix:<,> 是 list 结合性, 这个 1, 2, 3 会被转换成 infix:<,>(1; 2; 3). 还有 chain 的结合性: $a < $b < $c 会被转换成 ($a < $b) && ($b < $c).
  1. multi sub infix:<foo> is tighter(&infix:<+>)
  2.                       is assoc('left')
  3.                       ($a, $b) {
  4.     ...
  5. }
复制代码
Postcircumfix 和 Circumfix

Postcircumfix 操作符是方法调用:
  1. class OrderedHash is Hash {
  2.     method postcircumfix:<{ }>(Str $key) {
  3.         ...
  4.     }
  5. }
复制代码
如果你调用 $object{$stuff}, 这个 $stuff 会当成参数传给方法调用, 这个 $object 是一个可用的 self.

Circumfix 的调用意味着这有不同的语法 ( 象 my @list = <a b c>; ), 是用于实现宏 (macros):
  1. macro circumfix:«< >»($text) is parsed / <-[>]>+ / {
  2.     return $text.comb(rx/\S+/);
  3. }
复制代码
这的 is parsed 特性后面跟一个正则表达式解析分隔符之间的所有东西. If no such rule is given, it is parsed as normal Perl 6 code (which is usually not what you want if you introduce a new syntax).

Str.comb 的给一个正则表达式, 并返回所有匹配的文本列表.
"重载" 现有的操作符

很多 (不是全部) 现有的操作符是使用的 multi subs 和方法调用, 所以可以定制新的类型. 添加一个新的 multi sub 就能实现重载操作符了.
  1. class MyStr { ... }
  2. multi sub infix:<~>(MyStr $this, Str $other) { ... }
复制代码
这也意味着你可以重写看起来是内置对象的特殊对象, 比如 Str, Int etc.

动机

允许用户声明新的操作符和 "重载" 现有的用户定义的类型这非常强大. 如果内置的不合适你使用, 你可以使用自己的新的操作符, 无需编译器本身做任何改变.

它也消除使用语言和修改语言之间的差距.

另请参阅

http://perlcabal.org/syn/S06.html#Operator_overloading

如果你有兴趣了解相关的技术背景, 即 Perl 6 怎么实现这些操作符和其它 grammer 的改变, 你可以读读 http://perlgeek.de/en/article/mutable-grammar-for-perl-6.

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-20 16:38 |显示全部楼层
本帖最后由 iakuf 于 2014-05-20 16:38 编辑

"Perl 5 to 6" Lesson 14 - The MAIN sub

概要

  1. # file doit.pl

  2. #!/usr/bin/perl6 sub MAIN($path, :$force, :$recursive, :$home = '~/') { # do stuff here }

  3. # command line $ ./doit.pl --force --home=/home/someoneelse file_to_process
复制代码
描述

调用子函数和运行一个类 Unix 的命令行程序从视觉上看, 其实非常相似: 你可以有选项, 可选参数和命名参数.

使用 Perl 6 你可以从中利益很多, 它可以直接处理你的命令行, 并变成一个子函数调用. 你只要你的文件中存在 MAIN 的子函数, 你的脚本就可以正常的执行 ( 此时会给你的参数送给 @*ARGS ).

如果你提交的参数在那个 MAIN 的函数中并不对应, 会自动的帮你生成 usage 的信息.

命令行的参数映射到子程序的参数是这样工作的:
  1. -name :name -name=value :name

  2. # remember, <...> is like qw(...) --hackers=Larry,Damian :hackers

  3. --good_language :good_language --good_lang=Perl :good_lang --bad_lang PHP :bad_lang

  4. +stuff :!stuff +stuff=healty :stuff but False
复制代码
这个地方的 $x = $obj but False 的意思是这个 $x 是复制的 $obj, 但是在布尔上下文时会给出 Bool::False.

因此, 对于简单的 (和一些不是很简单的) 情况下, 你并不需要一个外部命令行参数的处理程序, 你可以直播使用 MAIN 函数实现这一点.

动机

这背后的动机应该是很明显的: 它使简单的事情变得更加容易, 很多时候给命令行代码传参只需要简单的 MAIN 函数.

另请参阅

http://perlcabal.org/syn/S06.html#Declaring_a_MAIN_subroutine contains the specification.

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-20 16:41 |显示全部楼层
本帖最后由 iakuf 于 2014-05-20 16:41 编辑

"Perl 5 to 6" Lesson 15 - Twigils 标记

概要

  1. class Foo { has $.bar; has $!baz; }

  2. my @stuff = sort { $^b[1] <=> $^a[1]}, [1, 2], [0, 3], [4, 8]; my $block = { say "This is the named 'foo' parameter: $:foo" }; $block(:foo);

  3. say "This is file $?FILE on line $?LINE"

  4. say "A CGI script" if %*ENV.exists('DOCUMENT_ROOT');
复制代码
描述

在 Perl 6 中有些变量有二个标记, 这个叫 twigil 标记. 如果它存在, 意味着这个变量不是 "标准" 的变量, 它和普通的有些不同, 例如, %*ENV 这个中的 * 表示是全局的, 所以有不同的作用域, 因为默认都不是全局的.

你见到在对象属性中公有和私有分别使用 . 和 ! 的 twigil 标记; 这指出他们不是正常的变量, 他们在这是和 self 绑定在一起的变量.

The ^ twigil removes a special case from perl 5. To be able to write
  1. # beware: perl 5 code sort { $a <=> $b } @array
复制代码
变量 $a 和 $b 在程序中使用了 strict 的时候来讲是个特例. 在 Perl 6 中, 有个概念叫 self-declared positional parameter, 这的参数有个 ^ 的标记. 它用于定位块中参数的位置, 它不是参数, 它只是用这些变量根据字母顺序填充位置.

  1. my $block = { say "$^c $^a $^b" }; $block(1, 2, 3); # 3 1 2
复制代码
所以你现在可以写成:
  1. @list = sort { $^b <=> $^a }, @list; # or: @list = sort { $^foo <=> $^bar }, @list;
复制代码
在正常的情况下, 用于保持定位和命令参数之间的对称, 在 : twigil 也是对命 名参数做同样的事情, 所以这些行相当于:
  1. my $block = { say $:stuff } my $sub = sub (:$stuff) { say $stuff }
复制代码
这的 ? 的 twigil 标记表示在编译的时候就知道的变量和常量, 象 $?LINE 是指当前的行的行号( 原 __LINE__ ), $?DATA 是指文件句柄中的 DATA 区.

( Contextual variables )上下文变量它是用 * 的 twigil 标记来标识, 可以用 $* 来访问, 根据上下文的变化而变化的, 先会搜索当前上下文, 再搜索被调用函数的上下文, 以此类推, 所以 $*IN 和 $*OUT 的输出能动态的覆盖.

有个伪 twigil 是 <, 它是象 $<capture> 的一个结构, 这是 $/<capture> 的简写, 用于访问正则表达式匹配了对象后的 Match object.

动机

当你读 Perl 5 中的 perlvar 文档的时候, 你可以知道, 这有很多的变量, 很多是全局变量, 会影响你的程序.

这的 twigils 会尝试给指定的变量某种顺序, 在另一方面, 他们消除了一些必要的特例. 在对象属性中 twigil 主要用于缩短 self.var 到 $.var (or @.var or whatever).

所以, 这些虽然增加了一些 "标点符号的噪音" 但实际上使得程序更加一致和可读.

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-22 09:56 |显示全部楼层
"Perl 5 to 6" Lesson 16 - Enums 枚举类型

概要
  1. enum bit Bool ; my $value = $arbitrary_value but True; if $value { say "Yes, it's true"; # will be printed }

  2. enum Day ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'); if custom_get_date().Day == Day::Sat | Day::Sun { say "Weekend"; }
复制代码
描述

枚举是通用的. 它是低层次类, 它可以包含常量, 整数和字符串.

这种常量可以当成子类型 (subtype), 方法和普通的值. 这些可以通过 but 操作符, 加入到对象, 看成混合了 enum 到变量:
  1. my $x = $today but Day::Tue;
复制代码
你也可以使用枚举类型的名字作为一个函数, 并提供一些值做为参数:
  1. $x = $today but Day($weekday);
复制代码
之后这个对象有一个方法, 可以通过枚举类型的名字来调用, 在这是 Day:
  1. say $x.Day; # 1
复制代码
第一个常量的值是 0, 接下来是 1 等等, 除非你明确的提供了键值对:
  1. enum Hackers (:Larry, :Guido, :Paul);
复制代码
你可以使用智能匹配操作符来检查特定的值. 或者使用 .does:
  1. if $today ~~ Day::Fri { say "Thank Christ it's Friday" }

  2. if $today.does(Fri) { ... }
复制代码
注意, 你可以指定唯一值的名字象 Fri. 如果你不确认你需要提供全名, 象 Day::Fri.

动机

枚举在 perl6 中做了两件事, 一个是 tainted 变量 ( 污染检查相关? ), 另一个是函数返回值问题. 这个问题就是: 很多函数用 return 0 返回, 这个时候函数返回的是 true.

Enums 也为 debuggin 和 tracing 提供强大和方便附加元数据的功能.

另请参阅

http://perlcabal.org/syn/S12.html#Enumerations

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-22 12:12 |显示全部楼层
"Perl 5 to 6" Lesson 17 - Perl 6 的 Unicode 字符编程

概要

(none)

描述

Perl 5 中的 Unicode 模型有个大问题: 它对于二进制和文本是使用的相同的类型. 例如, 你的程序从网络上的 socket 套接字上读取了 512 个字节, 它一定是一个 byte 串. 但是在 Perl 5 中对这个调用 uc 时, 就会被当成是文本. 推荐的方式是, 先对这个串进行解码, 但是做为子程序, 你接收一个串做为参数时, 你不能可靠的知道, 这到底是被解码了还是没有.

在 Perl 6 中提供了 buf 类型, 这只是 bytes 的集合, 也提供了 Str, 这些是 logical character 字符集合.

logical character仍然是一个模糊的术语. 为了更加准确的解释 Str 的对象, 我们可以见到几个不同的层次: Byte, Codepoint ( 任何 Unicode 的指定的数字都是一个 Codepoint ), Grapheme ( 在视觉上显示为一个字符 ) 和 CharLingua ( 语言上定义的字符 ).

例如, 字符串通过十六进制表示 61 cc 80 是由三个字位元组组成, 但也可以二个 codepoints 组成的名为 LATIN SMALL LETTER A (U+0041) 和 COMBINING GRAVE ACCENT (U+0300), 或者一个 grapheme , 如果你的浏览器能显示, 它看起来象 à.

所以你不能简单地取这个字符串的长度, 你要问一个特定类型的长度:
  1. $str.bytes; $str.codes; $str.graphs;
复制代码
这还有一个方法名为 chars, 这是返回当前 Unicode 级别的长度, 你可以通过在程序上给个 use bytes 的编译指示来设置为默认 graphemes.

在 Perl 5 中有时很容易错误的串联字符串流和文本字符串. 如果你在 Perl 6 也出现了这个问题, 你可以简单的通过重载连接运算符来简单的识别问题发生在哪.
  1. sub GLOBAL::infix:<~> is deep (Str $a, buf $b)|(buf $b, Str $a) {
  2.     die "Can't concatenate text string «" ~ $a.encode("UTF-8") "» with byte string «$b»\n";
  3. }
复制代码
编码和解码

目前 spec 文档中对于 IO 系统非常的低层, 并没有任何的编码和解码层, 这也是为什么这篇文章没有介绍这个部分, 不过肯定, 很快会有下面这样的一种机制:
  1. my $handle = open($filename, :r, :encoding);
复制代码
正则和 Unicode

正则表达式可以指定 Unicode level, 所以使用 m:codes/./ 会匹配到 codepoint. 如果没有指定 Unicode level 会使用当前的 Unicode level.

字符类象 \w ( 匹配单词字符 ) 会相应的使用 Unicode 标准. 象忽略大小写 (:i) 和重音符 (:a) 和一些会携带找到的模式的替换字符串操作 (:samecase and :sameaccent, short :ii, :aa) 都支持.

动机

对于大多数编程语言和工具来讲, 能很正确的处理字符是十分困难的. 例如, 在 Perl 5 的 web 应用程序中, 你想使用象 substr 来进行字符的切割时, 很有可能它会撕裂整个字符, 变成半个.

Perl 6 会是第一个内置对 grapheme 级别的字符进行操作的编程语言, 这基本上消除了大部分的你对 Unicode 的担忧, 并和正则表达式相结合, 会成为最强大的字符串处理语言.

对于文本和 byte 字符的数据类型的调试和内省会容易多了.

另请参阅

http://perlcabal.org/syn/S32/Str.html

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-23 10:02 |显示全部楼层
"Perl 5 to 6" Lesson 18 - Scoping 作用域

概要
  1. for 1 .. 10 -> $a {
  2.     # $a 在这可用
  3. }
  4. # $a 在这不可用

  5. while my $b = get_stuff() {
  6.     # $b 在这可用
  7. }
  8. # $b 在这仍然可用

  9. my $c = 5;
  10. {
  11.     my $c = $c;
  12.     # $c 在这是没有定义的
  13. }
  14. # $c 在这是 5

  15. my $y;
  16. my $x = $y + 2 while $y = calc();
  17. # $x 这仍然可用
复制代码
描述

语法作用域

Perl 6 中的作用域和 Perl 5 中的比较相似, 一个块会生成一个新的词法范围. 变量名搜索是从最里面的词法范围开始, 如果没有找到, 就查找接下来的外部范围. 就象 Perl 5 中的 my 声明的变量是一个词法变量, 如果使用 our 声明的其实是引入一个包变量的别名的词法变量.

这也有一些细微的差别: 变量在一个代码块的开始位置声明的, 在块的其它位置都可以见到 ( 例如: 在一个 while 循环的条件中 );

Perl 6 永远只能查找到词法范围内所限定名称 ( 名称包含变量和函数 ).

如果你想限制作用域, 你可以在块上使用形式参数:
  1. if calc() -> $result {
  2.     # 你可以在这使用 $result
  3. }
  4. # $result 在这就不可用了
复制代码
变量在生名后立即可用, 但是 Perl 5 需要在行结尾 ( 可能是分号处 ) 才能使用.
  1. my $x = .... ;
  2.         ^^^^^
  3.         $x visible here in Perl 6
  4.         but not in Perl 5
复制代码
动态作用域

原来的 local 关键字, 现在叫 temp, 使用这个, 如果你没有对这个变量进行初始化, 它这个提供的值是上一个名字空间的原来的值 ( 并不会为 undef );

此外还有一种新的被称为 hypothetical 的动态作用域的变量. 如果块中出现了异常, 接下来这个变量会先恢复先前的值, 如果没有, 就保持新的值.
Context variables 上下文变量

在 Perl 5 里, $!, $_ 是全局变量, 但在 Perl6 里就是上下文变量, 就是根据上下文的变化而变化的, 在 Perl6 中就是所谓的动态作用域 (dynamic scoping).

这解决了一个在 Perl 5 中的老问题, 在 Perl 5 中的 DESTROY 的子函数可以在一个代码块中被调用来退出, 这个会改变全局的变量, 例如下面的错误.

  1. # Broken Perl 5 code here: sub DESTROY { eval { 1 }; }

  2. eval { my $x = bless {}; die "Death\n"; }; print $@ if $@; # No output here
复制代码
在 Perl 6 中对于这个问题就不在使用隐式的全局变量了.

( 在 Perl 5.14 中, 有其它的方式来保护 $@ 的修改, 可以避免这种危害 )

伪包

对于一个块中, 想访问词法变量范围之外 ( 块内访问块外的词法变量 ) 相同名字变量, 可以使用 OUTER 这个伪包, 方法如下:
  1. my $x = 3;
  2. {
  3.     my $x = 10;
  4.     say $x;             # 10
  5.     say $OUTER::x;      # 3
  6.     say OUTER::<$x>     # 3
  7. }
复制代码
同样函数可以通过 CALLER 和 CONTEXT 来访问伪包的内容. 这不同之处在于 CALLER 只访问最接近的 caller 的命名空间的变量, CONTEXT 的工作方式很象 UNIX 的环境变量, 得到的是上下文变量的值. 当使用上下文的伪包调用时, 需要在变量声明的时候使用 is context.

动机

我们知道操作和使用全局变量并不好, 会引起很多问题. 我们目前有很多更加好的作用域的机制来实现, 因此, 全局变量仅用于本质上是全局数据, 象 %*ENV 或者 $*PID.

代码块作用域规则可以极大的简化我们对这些的处理.

下面引用了一段 Perl 5 中 perlsyn 的文档, 我不希望 Perl 6 中还有类似的东西:

NOTE: The behaviour of a "my" statement modified with a statement modifier conditional or loop construct (e.g. "my $x if ...") is undefined. The value of the "my" variable may be "undef", any previously assigned value, or possibly anything else. Don't rely on it. Future versions of perl might do something different from the version of perl you try it out on. Here be dragons.

另请参阅

S04 中有关变量范围: http://perlcabal.org/syn/S04.html.

S02 中所有有的伪包和说明上下文的范围的内容: http://perlcabal.org/syn/S02.html#Names.

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-23 10:04 |显示全部楼层
"Perl 5 to 6" Lesson 19 - 正则的反击

概要

  1. # normal matching:
  2. if 'abc' ~~ m/../ {
  3.     say $/;                 # ab
  4. }

  5. # 匹配通过隐含的 :sigspace 修饰符
  6. if 'ab cd ef'  ~~ mm/ (..) ** 2 / {
  7.     say $1;                 # cd
  8. }

  9. # substitute with the :sigspace modifier
  10. my $x = "abc     defg";
  11. $x ~~ ss/c d/x y/;
  12. say $x;                     # abx     yefg
复制代码
描述

由于正则表达式的基本知识都已经涵盖在 lesson 07, 这里有关于正则表达式一些有用的 ( 但不是很有条理 ) 补充.
匹配

匹配正则表达式时你并不一定需要编写 grammars, 传统的 m/.../ 现在还是可以接着使用的, 并且还多了个新的兄弟使法, 就是使用 mm/.../, 这是隐含的是使用了 :sigspace 修饰符. 记住, 这意味着空格在正则表达式中由 <.ws> 规则代替的.

<.ws> 是指, 如果在单词的中间有空格, 默认的规则是匹配 \s+, 其它的时候匹配 \s*.

这的 :samespace 修饰符需要非常小心, 空格匹配的 ws 规则会做为象分割符一样的槽. 这个匹配空格被复制到右侧的相应位置, 然后给中间的字符按前后进行置换. 同样 :samecase 修饰符可以简写成 :ii, 并且会保存前大小写的信息.
  1. my $x = 'Abcd';
  2. $x ~~ s:ii/^../foo/;
  3. say $x;                     # Foocd
  4. $x = 'ABC'
  5. $x ~~ s:ii/^../foo/;
  6. say $x                      # FOO
复制代码
例如, 如果你环境变量中很多是大写, 这时你想替换象模块名 Foo 到 Bar 之类的场合非常有用, 因为使用 :ii 修饰符会自动保存大小写的信息.

这种复制字符对应字符的情况, 这有很多智能的版本; 当绑定使用 :sigspace (短写 :s) 修饰符, 它会尝试发现原来字符的模式. 识别出.lc, .uc, .lc.ucfirst, .uc.lcfirst 和 .lc.capitaliz ( Str.capitalize 是每个单词的首字母大写 ). 只要识别出前面这几种模式就会给这些模式应用到后面替换的字符串上.
  1. my $x = 'The Quick Brown Fox';
  2. $x ~~ s :s :ii /brown.\*/perl 6 developer/;
  3. # $x is now 'The Quick Perl 6 Developer'
复制代码
Alternations 或匹配

匹配多个条件交替 |, 但它和 Perl 5 比起来还是有些不同. 这个并不是按顺序匹配第一个, 它现在在正则中并行的去匹配, 并取匹配到的最长的一个.
  1. 'aaaa' ~~ m/ a | aaa | aa /;
  2. say $/                          # aaa
复制代码
虽然这可能看起来像一个微不足道的变化, 它影响深远, 并且这对于 grammars 的可扩展性是至关重要的的. 自从 Perl 使用 grammar 来进行解析, 它对于象 ++$a 这种会解析成单个 ++ 的标记, 不然会解析成二个 prefix:<+> 的标记.

对于旧的方式, 顺序匹配第一个找到的可以使用 ||:
  1. grammar Math::Expression {
  2.     token value {
  3.         | <number>
  4.         | '('
  5.           <expression>
  6.           [ ')' || { fail("Parenthesis not closed") } ]
  7.     }

  8.     ...
  9. }
复制代码
这的 { ... } 是执行的一个闭包, 并且它在表达式失败时调用闭包中的 fail 函数. 这个分支语句用于当前面的 ')' 没找到时才执行, 所以可以使用它来做正则解析错误的提示.

还有其它的方法来做或者的条件, 例如你想给一个数组做 "插值", 这时会使用或者的方式匹配它的值:
  1. $_ = '12 oranges';
  2. my @fruits = <apple orange banana kiwi>;
  3. if m:i:s/ (\d+) (@fruits)s? / {
  4.     say "You've got $0 $1s, I've got { $0 + 2 } of them. You lost.";
  5. }
复制代码
这还有另一种结构来自动匹配最长的或者条件: multi regexes. 这可以写 multi token name 或者 proto:
  1. grammar Perl {
  2.     ...
  3.     proto token sigil { * }
  4.     token sigil:sym<$> { <sym> }
  5.     token sigil:sym<@> { <sym> }
  6.     token sigil:sym<%> { <sym> }
  7.     ...

  8.    token variable { <sigil> <twigil>? <identifier> }

  9. }
复制代码
这个例子中的 multiple tokens 叫做 sigil, 会给 sym 做参数化. 当使用这个短名称, 如 sigil 时, 所有的这个标记会去找或者的条件. 你可能认为这个方式来写或者条件的匹配非常麻烦. 但它有个巨大的优势就是可以写 '$'|'@'|'%': 这是非常容易扩展的:
  1. grammar AddASigil is Perl {
  2.     token sigil:sym<!> { <sym> }
  3. }
  4. # wow, we have a Perl 6 grammar with an additional sigil!
复制代码
你也可以覆盖现有的规则:
  1. grammar WeirdSigil is Perl {
  2.     token sigil:sym<$> { '°' }
  3. }
复制代码
在这个 grammar 中的 sigil 是一个标量变量 °, 所以每当 grammar 找到一个 sigil 它会查找 ° 而不是 $, 但编译器会知道你是使用了正则 sigil:sym<$> 来匹配.

接下来你的课程, 给你看一个真实的实例, 这个例子在 Rakudo 是可以正常工作的.

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-26 17:29 |显示全部楼层
"Perl 5 to 6" Lesson 20 - 处理 XML 的 grammar

概要
  1. grammar XML {
  2.     token TOP   { ^ <xml> $ };
  3.     token xml   { <text> [ <tag> <text> ]* };
  4.     token text {  <-[<>&]>* };
  5.     rule tag   {
  6.         '<'(\w+) <attributes>*
  7.         [
  8.             | '/>'                 # a single tag
  9.             | '>'<xml>'</' $0 '>'  # an opening and a closing tag
  10.         ]
  11.     };
  12.     token attributes { \w+ '="' <-["<>]>* '"' };
  13. };
复制代码
描述

到目前为止, 我们看到的都是 Perl 6 语言本身的一些实现的特性. 现在需要告诉你这不是一个纯粹 yy 的语言, 所以讲一些真实的应用, 来向你展示 Perl 强大的语法, 我们这课是使用 Rakudo 上的 grammar 来解析基本的 XML.

你可以看看 http://rakudo.org/how-to-get-rakudo/ 来怎么样安装这个.

Our idea of XML

对于我们的目标 XML 解析是非常简单的: 它是包含了一些纯文本和一些嵌套的的标签并能包含一些可选的属性. 所以我们先写几个可用和不可用的 "XML" 的测试.
  1. my @tests = (
  2.     [1, 'abc'                       ],      # 1
  3.     [1, '<a></a>'                   ],      # 2
  4.     [1, '..<ab>foo</ab>dd'          ],      # 3
  5.     [1, '<a><b>c</b></a>'           ],      # 4
  6.     [1, '<a href="foo"><b>c</b></a>'],      # 5
  7.     [1, '<a empty="" ><b>c</b></a>' ],      # 6
  8.     [1, '<a><b>c</b><c></c></a>'    ],      # 7
  9.     [0, '<'                         ],      # 8
  10.     [0, '<a>b</b>'                  ],      # 9
  11.     [0, '<a>b</a'                   ],      # 10
  12.     [0, '<a>b</a href="">'          ],      # 11
  13.     [1, '<a/>'                      ],      # 12
  14.     [1, '<a />'                     ],      # 13
  15. );

  16. my $count = 1;
  17. for @tests -> $t {
  18.     my $s = $t[1];
  19.     my $M = XML.parse($s);
  20.     if !($M  xor $t[0]) {
  21.         say "ok $count - '$s'";
  22.     } else {
  23.         say "not ok $count - '$s'";
  24.     }
  25.     $count++;
  26. }
复制代码
这会列出 "good" 和 "bad" 的 XML. 只需要调用 XML.parse($string) 来运行这些测试的的列表. 根据惯例这些匹配的 grammar 的语法, 需要符合 "TOP" 的该规则才行.

( 你可以看测试 1, 这个我们并没有 root 标签, 但我们会很容易来加入此限制 ).

grammar 开发

XML 的本质必然是标签嵌套, 所以我们给重点放在了第二个测试上面. 这个上面能使用这个测试脚本中的 top .
  1. grammar XML {
  2.     token TOP   { ^ <tag> $ }
  3.     token tag   {
  4.         '<' (\w+) '>'
  5.         '</' $0   '>'
  6.     }
  7. };
复制代码
现在运行测试脚本:
  1. $ ./perl6 xml-01.pl
  2. not ok 1 - 'abc'
  3. ok 2 - '<a></a>'
  4. not ok 3 - '..<ab>foo</ab>dd'
  5. not ok 4 - '<a><b>c</b></a>'
  6. not ok 5 - '<a href="foo"><b>c</b></a>'
  7. not ok 6 - '<a empty="" ><b>c</b></a>'
  8. not ok 7 - '<a><b>c</b><c></c></a>'
  9. ok 8 - '<'
  10. ok 9 - '<a>b</b>'
  11. ok 10 - '<a>b</a'
  12. ok 11 - '<a>b</a href="">'
  13. not ok 12 - '<a/>'
  14. not ok 13 - '<a />'
复制代码
所以这是使用了简单的规则来解析出了开始标签和结束标签对, 也正确的拒绝掉了无效的 XML 中的四个例子.

第一个测试应该很容易通过,所以让我们试试这个:
  1.    grammar XML {
  2.        token TOP   { ^ <xml> $ };
  3.        token xml   { <text> | <tag> };
  4.        token text  { <-[<>&]>*  };
  5.        token tag   {
  6.            '<' (\w+) '>'
  7.            '</' $0   '>'
  8.        }
  9.     };
复制代码
(记住, <-[...]> 是一个用于否定的字符类.)

现在运行:

  1. $ ./perl6 xml-03.pl
  2. ok 1 - 'abc'
  3. not ok 2 - '<a></a>'
  4. (rest unchanged)
复制代码
为什么处理第第二个测试就工作了? 答案是因为 Rakudo 还不支持 token 的长匹配 ( update 2013=01: 现在支持 ), 这个匹配是顺序的. <text> 匹配空字符串命中, 所以 <text> | <tag> 从来没有尝试匹配 <tag>, 给这二个顺序修改一下会有所帮助.

但我们并不只想无论怎么样都能匹配到纯文本或标签, 我们希望这二个能随机组合起来:
  1. token xml   { <text> [ <tag> <text> ]*  };
复制代码
([...] 是不会捕捉内容, 象在 Perl 5 中的 (?: ... ) ).

现在这前二个测试都能通过.

第三个测试, ..<ab>foo</ab>dd, 找有开头和结构标记之间的文本, 还要允许有多个标记, 因为不只是唯一中间可以放文本, 还要可以放任意的 XML. 因为这叫 <xml>.
  1. token tag   {
  2.     '<' (\w+) '>'
  3.     <xml>
  4.     '</' $0   '>'
  5. }
复制代码
  1. ./perl6 xml-05.pl
  2. ok 1 - 'abc'
  3. ok 2 - '<a></a>'
  4. ok 3 - '..<ab>foo</ab>dd'
  5. ok 4 - '<a><b>c</b></a>'
  6. not ok 5 - '<a href="foo"><b>c</b></a>'
  7. (rest unchanged)
复制代码
我们现在可以专注于属性的实现 ( href="foo" ):
  1. token tag   {
  2.     '<' (\w+) <attribute>* '>'
  3.     <xml>
  4.     '</' $0   '>'
  5. };
  6. token attribute {
  7.     \w+ '="' <-["<>]>* \"
  8. };
复制代码
这并不会有新的测试通过. 原因是因为签名和属性之间的有空白. 我们不使用 \s+ 或 \s* 而是使用 token 转到 rule, 所以这会有 :sigspace 的修饰符:
  1. rule tag   {
  2.     '<'(\w+) <attribute>* '>'
  3.     <xml>
  4.     '</'$0'>'
  5. };
  6. token attribute {
  7.     \w+ '="' <-["<>]>* \"
  8. };
复制代码
现在,所有的测试都通过了,除了最后两个:
  1. ok 1 - 'abc'
  2. ok 2 - '<a></a>'
  3. ok 3 - '..<ab>foo</ab>dd'
  4. ok 4 - '<a><b>c</b></a>'
  5. ok 5 - '<a href="foo"><b>c</b></a>'
  6. ok 6 - '<a empty="" ><b>c</b></a>'
  7. ok 7 - '<a><b>c</b><c></c></a>'
  8. ok 8 - '<'
  9. ok 9 - '<a>b</b>'
  10. ok 10 - '<a>b</a'
  11. ok 11 - '<a>b</a href="">'
  12. not ok 12 - '<a/>'
  13. not ok 13 - '<a />'
复制代码
这些包含在封闭的标签中有单个斜线 /. 这个很容易, 我们只要在我们的 rule 的 tag 中加入这个就好:
  1. rule tag   {
  2.     '<'(\w+) <attribute>* [
  3.         | '/>'
  4.         | '>' <xml> '</'$0'>'
  5.     ]
  6. };
复制代码
所有的测试都通过了, 我们很高兴, 我们的第一个 grammar 效果很好.

More hacking

Playing with grammars is much more fun that reading about playing, so here's what you could implement:

    plain text can contain entities like &amp;
    I don't know if XML tag names are allowed to begin with a number, but the current grammar allows that. You might look it up in the XML specification, and adapt the grammar if needed.
    plain text can contain <![CDATA[ ... ]]> blocks, in which xml-like tags are ignored and < and the like don't need to be escaped
    Real XML allows a preamble like <?xml version="0.9" encoding="utf-8"?> and requires one root tag which contains the rest (You'd have to change some of the existing test cases)
    You could try to implement a pretty-printer for XML by recursively walking through the match object $/. (This is non-trivial; you might have to work around a few Rakudo bugs, and maybe also introduce some new captures).

(Please don't post solutions to this as comments in this blog; let others have the same fun as you had ;-).

Have fun hacking.

动机

这个很强大并且有意思

另请参阅

Regexes are specified in great detail in S05: http://perlcabal.org/syn/S05.html.

More working examples for grammars can be found at https://github.com/moritz/json/ (check file lib/JSON/Tiny/Grammar.pm).

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2014-05-26 17:30 |显示全部楼层
"Perl 5 to 6" Lesson 21 - Subset Types 子类型

概要
  1. subset Squares of Int where { .sqrt.Int**2 == $_ };

  2. multi sub square_root(Squares $x --> Int) {
  3.     return $x.sqrt.Int;
  4. }
  5. multi sub square_root(Num $x --> Num) {
  6.     return $x.sqrt;
  7. }
复制代码
描述

Java 程序员倾向于认为类型就是一个类或者接口 ( 这有点像一个残缺的类 ), 但这种观点是因为对 Perl 6 的了解太有限了. 类型普通普通看法是对容器中可以放什么值进行约束.

传统的一个约束是, 某个东西, 它是一个 X 类的对象或者它是从 X 继承, Perl 6 也有类似的限制象类或者对象 does role Y, 或者这一段代码返回 true 为我们的对象. 这个我们在这叫 subset 类型.
  1. subset Even of Int where { $_ % 2 == 0 }
  2. # Even 现在是可以象其它类型一样使用了

  3. my Even $x = 2;
  4. my Even $y = 3; # 类型不匹配错误
复制代码
( 试试, Rakudo 实现的子类型 ).

你也可以在子函数中使用匿名的子类型.
  1. sub foo (Int where { ... } $x) { ... }
  2. # or with the variable at the front:
  3. sub foo ($x of Int where { ... } ) { ... }
复制代码
动机

代码的终极可扩展性就是许任意类型的约束形式: 如果你不喜欢现有的类型系统, 你可以使用基于自己创建的子类型.

这也使的 lib 库更加容易扩展: 用来替换 die 处理不正常的数据, 在子函数和方法上只需要声明所需要的类型的方式, 不正常的数据就会被 multi dispatcher 所拒绝. 如果其它人想处理提交要处理的坏数据时, 因只需要简单的指定所需要的子类型, 就可以正常的工作. 例如, 你可以写一个 lib 库用于处理实数, 你可以通过这种方式扩展, 让他可以处理其它复杂的数字.
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP