• S07


    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 返回的同一性。

  • 相关阅读:
    js技巧
    四层代理和七层代理
    【转载】SEDA高性能互联网服务器架构模型(1)
    Python3教程:math 模块的用法
    Python3基础教程:return和yield的区别
    valueOf()与toString()
    tauri + vue first app
    nvm和yarn的安装
    websocket及心跳检测
    python 列表
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12046767.html
Copyright © 2020-2023  润新知