pushappend 的表现不同, 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]

这跟The single argument rule有关。

设计大纲


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 是不可能的。 reverserotate 操作会返回新的 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 类型也可以用在 mapgather/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


ArrayList 的一个子类, 把赋值给数组的值放进 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支持 pushpopshiftunshiftsplice

给数组赋值默认是迫切的(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;

然而, 任何重用 linesbeer、或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


SeqList 这俩, 还有 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的更多相关文章

  1. scrapy爬虫结果插入mysql数据库

    1.通过工具创建数据库scrapy

  2. PHP+ajaxfileupload与jcrop插件结合 完成头像上传

    昨天花了点时间整合了一下头像插件 东拼西凑的成果 先来看下效果

  3. PostgreSQL系列一:PostgreSQL简介与安装

    一.PostgreSQL简介     1.1 PostgreSQL概述             PostgreSQL数据库是目前功能最强大的开源数据库,支持丰富的数据类型(如JSON和JSONB类型. ...

  4. Tomcat双向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端超安全通信

    紧接着<Tomcat单向Https验证搭建,亲自实现与主流浏览器.Android/iOS移动客户端安全通信>,此处演示下更安全的双向Https认证的通信机制,为了清晰明了,以下进行单独描述 ...

  5. Tomcat单向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端安全通信

    众所周知,iOS9已经开始在联网方面默认强制使用Https替换原来的Http请求了,虽然Http和Https各有各的优势,但是总得来说,到了现在这个安全的信息时代,开发者已经离不开Https了. 网上 ...

  6. 数据库逆向框架代码生成工具:MyBatis Generator的使用

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration ...

  7. UGUI之Toggle使用

    Toggle对象是一个开关.一般用于单选,可以用Toggle制作背包的选项卡 在场景中创建Toggle按钮.看看他有Toggle组件

  8. groovy

    1.加载和卸载(每次都新建一个GroovyClassLoader 实例,然后使用新建的classloader去加载) try { GroovyClassLoader groovyClassLoader ...

  9. OAF_EO系列6 - Delete详解和实现(案例)

    2014-06-14 Created By BaoXinjian

随机推荐

  1. 注册服务和发现服务 Eureka

    来自蚂蚁课堂: 注册服务和发现服务 1.原理如图: 注册中心负载均衡: 实践 注册中心 集群:

  2. base64字符串转化成图片

    package com.dhht.wechat.util; import sun.misc.BASE64Decoder;import sun.misc.BASE64Encoder; import ja ...

  3. callable和runnable的区别

    Runnable接口源码 @FunctionalInterface public interface Runnable { /** * When an object implementing inte ...

  4. 《VSTO开发中级教程》刘永富 著 清华大学出版社 在线购买

    现在可以和作者 刘永富 通过“二手书直卖”这个APP直接买书. 二手书直卖 的下载方法: 方法一:加QQ群61840693,群共享中搜索“二手书直卖”,下载后打开即可. 方法二:从本帖下载:二手书直卖 ...

  5. UUID与时间戳

    /** * 32位去除'-'的UUID */ public static String getUUID() { String uuid = java.util.UUID.randomUUID().to ...

  6. 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: ...

  7. 吴裕雄--天生自然python学习笔记:python 用pygame模块动画一让图片动起来

    动画是游戏开发中不可或缺的要素,游戏中的角色只有动起来才会拥有“生命”, 但动画处理也是最让游戏开发者头痛的部分.Pygame 包通过不断重新绘制绘图窗口,短短几行代码就可以让图片动起来! 动画处理程 ...

  8. P2448 无尽的生命(树状数组+离散化)

    题目描述 逝者如斯夫,不舍昼夜! 叶良辰认为,他的寿命是无限长的,而且每天都会进步. 叶良辰的生命的第一天,他有1点能力值.第二天,有2点.第n天,就有n点.也就是S[i]=i 但是调皮的小A使用时光 ...

  9. CentOS6与CentOS7的网络区别

    回顾:物理层 关注的是接口物理特性,传输介质数据链路层 MAC地址,数据帧,以太网,交换机网络层 IP地址,数据包,IP\ICMP\ARP协议,路由器传输层 TCP.UDP,端口号,数据段应用层 HT ...

  10. maven setting.xml说明

    <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...