S07
push 和 append 的表现不同, push 一次只添加单个参数到列表末端, append 一次可以添加多个参数。
use v6;
my @d = ( [ 1 .. 3 ] );
@d.push( [ 4 .. 6 ] );
@d.push( [ 7 .. 9 ] );
for @d -> $r {
say "$r[]";
}
# 1
# 2
# 3
# 4 5 6
# 7 8 9
for @d -> $r { say $r.WHAT() }
# (Int)
# (Int)
# (Int)
# (Array) 整个数组作为单个参数
# (Array)
say @d.perl;
# [1, 2, 3, [4, 5, 6], [7, 8, 9]]
使用 append 一次能追加多个元素:
use v6;
my @d = ( [ 1 .. 3 ] );
@d.append( [ 4 .. 6 ] );
@d.append( [ 7 .. 9 ] );
for @d -> $item {
say "$item[]";
}
# 打印 1n2n3n4n5n6n7n8n9
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
设计大纲
Perl 6 提供了很多跟列表相关的特性, 包括 eager
, lazy
, 并行计算
, 还有紧凑型阵列和多维数组存储。
Sequences vs. Lists
在 Perl 6 中, 我们使用项 sequence
来来指某个东西, 当需要的时候产生一个值的序列。即序列是惰性的。注意, 只能要求生成一次。我们使用项list
来指能保存值的东西。
(1, 2, 3) # 列表, 最简单的 list
[1, 2, 3] # 数组, Scalar 容器的列表
|(1, 2) # a Slip, 一个展开到周围列表中的列表
$*IN.lines # a Seq, 一个可以被连续处理的序列
(^1000).race # a HyperSeq, 一个可以并行处理的序列
The single argument rule
在 Perl 中 @ 符号标示着 “这些”(these), 而 $符号标示着 “这个”(the)。这种复数/单数的明显差别出现在语言中的各种地方, Perl 中的很多便捷就是来源于这个特点。展平(Flattening)就是 @-like 的东西会在特定上下文中自动把它的值并入周围的列表中。之前这在 Perl 中既功能强大又很有迷惑性。在直截了当的发起知名的”单个参数规则”之前, Perl 6 在发展中通过了几次跟 flattening 有关的模仿。
对单个参数规则最好的理解是通过 for循环迭代的次数。对于 for循环, 要迭代的东西总是被当作单个参数。因此有了单个参数规则这个名字。
for 1, 2, 3 { } # 含有 3 个元素的列表, 3 次迭代
for (1, 2, 3) { } # 含有 3 个元素的列表, 3 次迭代
for [1, 2, 3] { } # 含有 3 个元素的数组(存放在 Scalar 中), 3次迭代
for @a, @b { } # 含有 2 个元素的列表, 2 次迭代
for (@a,) { } # 含有 1 个元素的列表, 1 次迭代
for (@a) { } # 含有 @a.elems 个元素的列表, @a.elems 次迭代
for @a { } # 含有 @a.elems 个元素的列表, @a.elems 次迭代
前两个是相同的, 因为圆括号事实上不构建列表, 而只是分组。是中缀操作符 infix:<,>
组成的列表。第三个也执行了 3 次迭代, 因为在 Perl 6 中 [...]
构建了一个数组但是没有把它包裹进 Scalar
容器中。第四个会执行 2 次迭代, 因为参数是一个含有两个元素的列表, 而那两个元素恰好是数组, 它俩都有 @ 符号, 但是都没有导致展开。第 五 个同样, infix:<,>
很高兴地组成了只含一个元素的列表。
单个参数规则也考虑了 Scalar 容器。因此:
for $(1, 2, 3) { } # Scalar 容器中的一个列表, 1 次迭代
for $[1, 2, 3] { } # Scalar 容器中的一个数组, 1 次迭代
for $@a { } # Scalar 容器中的一个数组, 1 次迭代
> for $(1, 2, 3) -> $i { say $i.elems }
3
> for $(1, 2, 3) -> $i { say $i }
(1 2 3)
> for $(1, 2, 3) -> $i { say $i.WHAT }
(List)
> for $(1, 2, 3) -> $i { say $i.perl }
$(1, 2, 3)
> for $[1, 2, 3] -> $i { say $i }
[1 2 3]
> for $[1, 2, 3] -> $i { say $i.perl }
$[1, 2, 3]
> for $[1, 2, 3] -> $i { say $i.WHAT }
(Array)
> for $[1, 2, 3] -> $i { say $i.elems }
3
> my @a = 1,2,3
[1 2 3]
> for $@a -> $a { say $a.perl }
$[1, 2, 3]
贯穿 Perl 6 语言, 单个参数规则(Single argument rule) 始终如一地被实现了。例如, 我们看 push 方法:
@a.push: 1, 2, 3; # pushes 3 values to @a
@a.push: [1, 2, 3]; # pushes 1 Array to @a
@a.push: @b; # pushes 1 Array to @a
@a.push: @b,; # same, trailing comma doesn't make > 1 argument
@a.push: $(1, 2, 3); # pushes 1 value (a List) to @a
@a.push: $[1, 2, 3]; # pushes 1 value (an Array) to @a
此外, 列表构造器(例如 infix:<,>
操作符) 和数组构造器([…]
环缀)也遵守这个规则:
[1, 2, 3] # Array of 3 elements
[@a, @b] # Array of 2 elements
[@a, 1..10] # Array of 2 elements
[@a] # Array with the elements of @a copied into it
[1..10] # Array with 10 elements
[$@a] # Array with 1 element (@a)
[@a,] # Array with 1 element (@a)
[[1]] # Same as [1]
[[1],] # Array with a single element that is [1]
[$[1]] # Array with a single element that is [1]
所以, 要让最开始的那个例子工作, 使用:
my @d = ( [ 1 .. 3 ], ); # [[1 2 3]]
@d.push: [ 4 .. 6 ];
@d.push: [ 7 .. 9 ];
# [[1 2 3] [4 5 6] [7 8 9]]
或者
my @d = ( $[ 1 .. 3 ]);
@d.push: [ 4 ..6 ];
@d.push: [ 7 ..9 ];
User-level Types
List
List 是不可变的, 可能是无限的, 值的列表。组成 List 最简单的一种方法是使用 infix:<,>
操作符:
1, 2, 3
List 可以被索引, 并且, 假设它是有限的, 也能询问列表中元素的个数:
say (1, 2, 3)[1]; # 2
say (1, 2, 3).elems; # 3
因为List是不可变的, 对它进行 push、pop、shift、unshift 或 splice 是不可能的。 reverse 和 rotate 操作会返回新的 Lists。
虽然List自身是不可变的, 但是它包含的元素可以是可变的, 包括 Scalar
容器:
my $a = 2;
my $b = 4;
($a, $b)[0]++;
($a, $b)[1] *= 2;
say $a; # 3
say $b; # 8
在 List 中尝试给不可变值赋值会导致错误:
(1, 2, 3)[0]++; # Dies: 不能给不可变值赋值
Slip
Slip 类型是 List 的一个子类。Slip 会把它的值并入周围的 List大专栏 S07strong> 中。
(1, (2, 3), 4).elems # 3
(1, slip(2, 3), 4).elems # 4
把 List 强转为 Slip 是可能的, 所以上面的也能写为:
(1, (2, 3).Slip, 4).elems # 4
在不发生 flattening 的地方使用 Slip 是一种常见的获取 flattening 的方式:
my @a = 1, 2, 3;
my @b = 4, 5;
.say for @a.Slip, @b.Slip; # 5 次迭代
这有点啰嗦, 使用 prefix:<|>
来做 Slip 强转:
my @a = 1, 2, 3;
my @b = 4, 5;
.say for |@a, |@b; # 5 次迭代
|
在如下形式中也很有用:
my @prefixed-values = 0, |@values;
这儿, 单个参数规则会使 @prefixed-values 拥有两个元素, 即 0 和 @values。
Slip 类型也可以用在 map
、gather/take
、和 lazy
循环中。下面是一种 map
能把多个值放进它的结果流里面的方法:
my @a = 1, 2;
say @a.map({ $_ xx 2 }).elems; # 2
say @a.map({ |($_ xx 2) }).elems; # 4
因为 $_ xx 2
产生一个含有两个元素的列表(List)。
Array
Array 是 List 的一个子类, 把赋值给数组的值放进 Scalar 容器中, 这意味着数组中的值可以被改变。Array 是 @-sigil 变量得到的默认类型。
my @a = 1, 2, 3;
say @a.WHAT; # (Array)
@a[1]++; # Scalar 容器中的值可变
say @a; # 1 3 3
如果没有 shape 属性, 数组会自动增长:
my @a;
@a[5] = 42;
say @a.elems; # 6
Array支持 push
、pop
、shift
、unshift
和 splice
。
给数组赋值默认是迫切的(eager), 并创建一组新的 Scalar 容器:
my @a = 1, 2, 3;
my @b = @a;
@a[1]++;
say @b; # 1, 2, 3
注意, [...]
数组构造器等价于创建然后再赋值给一个匿名数组。
Seq
Seq 是单次值生产者。大部分列表处理操作返回 Seq。
say (1, 2, 3).map(* + 1).^name; # Seq
say (1, 2 Z 'a', 'b').^name; # Seq
say (1, 1, * + * ... *).^name; # Seq
say $*IN.lines.^name; # Seq
因为 Seq 默认不会记住它的值(values), 所以 Seq 只能被使用一次。例如, 如果存储了一个 Seq:
my seq = (1, 2, 3).map(* + 1);
只有第一次迭代会有效, 之后再尝试迭代就会死, 因为值已经被用完了:
for seq { .say } # 2n3n4n
for seq { .say } # Dies: This Seq has already been iterated
这意味着你可以确信 for 循环迭代了文件的行:
for open('data').lines {
.say if /beer/;
}
这不会把文件中的行保持在内存中。此外设立不会把所有行保持在内存中的处理管道也会很容易:
my lines = open('products').lines;
my beer = lines.grep(/beer/);
my excited = beer.map(&uc);
.say for excited;
然而, 任何重用 lines
、beer
、或excited
的尝试都会导致错误。这段程序在性能上等价于:
.say for open('products').lines.grep(/beer/).map(&uc);
但是提供了一个给阶段命名的机会。注意使用 Scalar 变量代替也是可以的, 但是单个参数规则需要最终的循环必须为:
.say for |$excited;
只要序列没有被标记为 lazy
, 把 Seq 赋值给数组就会迫切的执行操作并把结果存到数组中。因此, 任何人这样写就不惊讶了:
my @lines = open('products').lines;
my @beer = @lines.grep(/beer/);
my @excited = @beer.map(&uc);
.say for @excited;
重用这些数组中的任何一个都没问题。当然, 该程序的内存表现完全不同, 并且它会较慢, 因为它创建了所有的额外的 Scalar 容器(导致额外的垃圾回收)和糟糕的位置引用。(我们不得不在程序的生命周期中多次谈论同一个字符串)。
偶尔, 要求 Seq 缓存自身也有用。这可以通过在Seq 身上调用 cache
方法完成, 这从 Seq 得到一个惰性列表并返回它。之后再调用 cache
方法会返回同样的惰性列表。注意, 第一次调用 cache
方法会被算作消费了Seq, 所以如果之前已经发生了迭代它就不再有效, 而且之后任何在调用完 cache
的迭代尝试都会失败。只有 .cache
方法能被调用多于1 次。
Seq 不像 List 那样遵守 Positional
role。 因此, Seq 不能被绑定给含有 @ 符号的变量:
my @lines := $*IN.lines; # Dies
这样做的一个后果就是, 原生地, 你不能传递 Seq 作为绑定给@符号的参数:
sub process(@data) {
}
process($*IN.lines);
这会极不方便。因此, 签名 binder(它实际使用 ::= 赋值语义而非 :=)会 spot 失败来绑定 @符号参数, 并检查参数是否遵守了 Positional role。 如果遵守了, 那么它会在参数上调用 cache 方法并绑定它的结果代替。
Iterable
Seq 和 List 这俩, 还有 Perl 6 中的各种其它类型, 遵守 Iterable role。这个 role 的主要意图是获得一个 iterator
方法。中级 Perl 6 用户很少会关心 iterator
方法和它返回什么。
Iterable 的第二个目的是为了标记出会被按需展开的东西, 使用 flat
方法或用在它们身上的函数。
my @a = 1, 2, 3;
my @b = 4, 5;
for flat @a, @b { } # 5 次迭代
say [flat @a, @b].elems; # 5 次迭代
flat 的另一用途是展开嵌套的列表结构。例如, Z(zip)操作符产生一个列表的列表:
say (1, 2 Z 'a', 'b').perl; # ((1, "a"), (2, "b")).Seq
flat 能用于展开它们, 这在和使用带有多个参数的尖块 for 循环一块使用时很有用:
for flat 1, 2 Z 'a', 'b' -> $num, $letter { }
注意 flat 也涉及 Scalar 容器, 所以:
for flat $(1, 2) { }
将只会迭代一次。记住数组把所有东西都存放在 Scalar 容器中, 在数组身上调用 flat 总是和迭代数组自身相同。实际上, 在数组上调用 flat 返回的同一性。
S07的更多相关文章
- scrapy爬虫结果插入mysql数据库
1.通过工具创建数据库scrapy
- PHP+ajaxfileupload与jcrop插件结合 完成头像上传
昨天花了点时间整合了一下头像插件 东拼西凑的成果 先来看下效果
- PostgreSQL系列一:PostgreSQL简介与安装
一.PostgreSQL简介 1.1 PostgreSQL概述 PostgreSQL数据库是目前功能最强大的开源数据库,支持丰富的数据类型(如JSON和JSONB类型. ...
- Tomcat双向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端超安全通信
紧接着<Tomcat单向Https验证搭建,亲自实现与主流浏览器.Android/iOS移动客户端安全通信>,此处演示下更安全的双向Https认证的通信机制,为了清晰明了,以下进行单独描述 ...
- Tomcat单向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端安全通信
众所周知,iOS9已经开始在联网方面默认强制使用Https替换原来的Http请求了,虽然Http和Https各有各的优势,但是总得来说,到了现在这个安全的信息时代,开发者已经离不开Https了. 网上 ...
- 数据库逆向框架代码生成工具:MyBatis Generator的使用
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration ...
- UGUI之Toggle使用
Toggle对象是一个开关.一般用于单选,可以用Toggle制作背包的选项卡 在场景中创建Toggle按钮.看看他有Toggle组件
- groovy
1.加载和卸载(每次都新建一个GroovyClassLoader 实例,然后使用新建的classloader去加载) try { GroovyClassLoader groovyClassLoader ...
- OAF_EO系列6 - Delete详解和实现(案例)
2014-06-14 Created By BaoXinjian
随机推荐
- 注册服务和发现服务 Eureka
来自蚂蚁课堂: 注册服务和发现服务 1.原理如图: 注册中心负载均衡: 实践 注册中心 集群:
- base64字符串转化成图片
package com.dhht.wechat.util; import sun.misc.BASE64Decoder;import sun.misc.BASE64Encoder; import ja ...
- callable和runnable的区别
Runnable接口源码 @FunctionalInterface public interface Runnable { /** * When an object implementing inte ...
- 《VSTO开发中级教程》刘永富 著 清华大学出版社 在线购买
现在可以和作者 刘永富 通过“二手书直卖”这个APP直接买书. 二手书直卖 的下载方法: 方法一:加QQ群61840693,群共享中搜索“二手书直卖”,下载后打开即可. 方法二:从本帖下载:二手书直卖 ...
- UUID与时间戳
/** * 32位去除'-'的UUID */ public static String getUUID() { String uuid = java.util.UUID.randomUUID().to ...
- HTTP Status 500 - Request processing failed; nested exception is java.lang.IllegalArgumentException: Control character in cookie value or attribute.
HTTP Status 500 - Request processing failed; nested exception is java.lang.IllegalArgumentException: ...
- 吴裕雄--天生自然python学习笔记:python 用pygame模块动画一让图片动起来
动画是游戏开发中不可或缺的要素,游戏中的角色只有动起来才会拥有“生命”, 但动画处理也是最让游戏开发者头痛的部分.Pygame 包通过不断重新绘制绘图窗口,短短几行代码就可以让图片动起来! 动画处理程 ...
- P2448 无尽的生命(树状数组+离散化)
题目描述 逝者如斯夫,不舍昼夜! 叶良辰认为,他的寿命是无限长的,而且每天都会进步. 叶良辰的生命的第一天,他有1点能力值.第二天,有2点.第n天,就有n点.也就是S[i]=i 但是调皮的小A使用时光 ...
- CentOS6与CentOS7的网络区别
回顾:物理层 关注的是接口物理特性,传输介质数据链路层 MAC地址,数据帧,以太网,交换机网络层 IP地址,数据包,IP\ICMP\ARP协议,路由器传输层 TCP.UDP,端口号,数据段应用层 HT ...
- maven setting.xml说明
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...