raku 中的操作符(二)

  • infix:<…>, 序列操作符.

作为一个中缀操作符, ... 操作符的左右两侧都有一个列表,并且为了产生想要的值的序列,序列操作符 ... 会尽可能地对序列进行惰性求值。列表被展平后求值。就像所有的中缀操作符一样, … 序列操作符比逗号的优先级要低,所以你没必要在逗号列表的两侧加上圆括号。

序列操作符 ... 以右侧列表的第一个值开始。这只在右侧的列表中, … 序列操作符唯一感兴趣的值;any additional list elements are treasured up lazily to be returned after the … is done.

… 右侧的第一个值是序列的端点或界限,这是序列操作符 … 从左侧生成的。

一旦我们知道了序列的边界,左侧的列表会一项一项地被求值,并且普通数字或字符串值被无差异地传递(在右侧边界允许的程度内)如果序列中的任何值匹配到边界值,序列会终止,包括那个最后的边界值在内。要排除边界值,使用 …^ 代替。

在内部,这两种形式用于检测匿名的循环是否会终止,而循环返回序列中的值。假设下一个候选的值存储在 $x 中,并且右侧序列中的第一个值存储在 $limit 中,这两个操作符各自实现为:

    ...     last($x) if $x ~~ $limit;
    ...^    last     if $x ~~ $limit;

如果边界是 * ,序列就没有界限。如果边界是一个闭包,它会在当前候选对象中进行布尔真值求值,并且序列会一直继续只要闭包返回false。如果边界是一个含有一个或无限参数的闭包,

    my $lim = 0;
    1,2,3 ...^ * > $lim      # returns (), since 1 > 0

这个操作符如果只能把左边的值原样返回就太乏味了。它的强大来自于能从旧值生成新值。你可以,例如,使用一个存在的生成器产生一个无穷列表:

    1..* ... * >= $lim
    @fib ... * >= $lim

例如:

> 1..* ... * >= 10  # 1 2 3 4 5 6 7 8 9 10

更一般地,如果 … 操作符左侧列表中的下一项是一个闭包,它不会被返回。他会在已经存在的列表的末尾被调用以产生一个新值。闭包中变量的数目决定了要使用多少前置值作为输入来生成序列中的下一个值。例如,以2为步长计数只需要一个参数:

    2, { $^a + 2 } ... *           # 2,4,6,8,10,12,14,16...

生成裴波纳契序列一次需要两个参数:

    1, 1, { $^a + $^b } ... *      # 1,1,2,3,5,8,13,21...

任何特定的函数也有效,只要你把它作为列表中的一个值而不是调用它:

    1, 1, &infix:<+> ... *         # 1,1,2,3,5,8...
    1, 1, &[+] ... *               # 同上
    1, 1, *+* ... *                # 同上
> sub infix:<jia>($a,$b){ return $a+$b }
> 1 jia 5 # 6
> 1,1,&[jia] ... * # 1 1 2 3 5 8 13 21 34 55 89 144 233 377 ......

更一般地,函数是一元的,这种情况下左侧列表中任何额外的值会被构建为人类可读的文档:

    0,2,4, { $_ + 2 } ... 42        #  0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42

使用闭包:

> 0,2,4,-> $init {$init+2} ... 42   # 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42
    0,2,4, *+2 ... 42           # same thing
    <a b c>, { .succ } ... *    # same as 'a'..*

函数也可以不是单调函数:

    1, -* ... *                # 1, -1, 1, -1, 1, -1...
    False, &prefix:<!> ... *   # False, True, False...

函数也可以是 0-ary 的, 这个时候让闭包成为第一个元素是 okay 的:

{ rand } ... *             # 一组随机数

函数还可以是吞噬的(n-ary),在这种情况下,所有前面的值都被传递进来(这意味着它们必须都被操作符缓存下来,所以性能会经受考验, 你可能会发现自己空间泄露了)

函数的数量不必与返回值的数量匹配, 但是如果确实匹配, 你可能会插入不相关的序列:

    1,1,{ $^a + 1, $^b * 2 } ... *   # 1,1,2,2,3,4,4,8,5,16,6,32...
    1,1, ->$a,$b {$a+1,$b*2} ... *  # 同上

注意在这种情况下任何界限测试被应用到从函数返回的整个 parcel 上, 这个 parcel 包含两个值。

除了那些函数签名所隐含的约束,序列操作符从一个没有对序列作类型约束的显式函数中生成。如果函数的签名和已存在的值不匹配,那么序列终止。

S03-sequence/misc.t lines 5–12

如果没有提供生成闭包, 并且序列是数字的,而且序列明显还是等差的或等比的(通过检查它的最后 3 个值),那么序列操作符就会推断出合适的函数:

1, 3, 5 ... *    # 奇数
1, 2, 4 ... *    # 2的幂
10,9, 8 ... 0    # 倒数

即, 假设我们调用了最后 3 个数字 $a, $b, 和 $c,然后定义:

$ab = $b - $a;
$bc = $c - $b;

如果 $ab == $bc 并且 $ab 不等于0, 那么我们通过函数 +$ab 可以推断出等差数列。如果 $ab 为 0,并且那3个值看起来像数字,那么函数推断为 +0

如果它们看起来不像数字,那么根据 $b cmp $c 的结果是 Increasing 还是 Decreasing 来决定选择的函数是 .succ 还是 .pred

如果 cmp 返回 Same 那么会假定函数是同一个。

如果 $ab != $bc 并且 none($a,$b,$c) == 0, 那么会使用除法而非减法做类似的计算来决定是否需要一个等比数列。定义:

$ab = $b / $a;
$bc = $c / $b;

如果两者的商相等(并且是有限的),那么会推断出等比函数 {$_ * $bc}

如果目前为止只有 2 个值, $a 和 $b, 并且差值 $ab 不为 0, 那么我们就假定函数为 *+$ab 的等差数列。

如果 $ab 是 0, 那么再次, 我们使用 +0 还是 .succ/*.pred 取决于那两个值看起来是不是数字。

如果只有一个值,我们总是通过 *.succ 假定增量(这可能被强制为 .pred 通过检查界限,就像下面指定的那样)因此这些结果都是一样的:

1 .. *
1 ... *
1,2 ... *
1,2,3 ... *
<1 2 3> ... *

同样地, 如果给定的值(s)不是数字, 就假定为 .succ, 所以这些结果是一样的:

'a' .. *
'a' ... *
'a','b' ... *
'a','b','c' ... *
<a b c> ... *

如果序列操作符的左侧是 (), 那我们使用函数 {()} 来生成一个无限的空序列。

如果给定了界限,那这个界限就必须被精确匹配。如果不能精确匹配,会产生无限列表。例如,因为”趋近”和“相等”不一样, 下面的两个序列都是无限列表,就像你把界限指定为了 * 而不是 0:

1,1/2,1/4 ... 0    # like 1,1/2,1/4 ... *
1,-1/2,1/4 ... 0   # like 1,-1/2,1/4 ... *

同样地,这是所有的偶数:

my $end = 7;
0,2,4 ... $end

为了捕捉这样一种情况, 建议写一个不等式代替:

0,2,4 ...^ { $_ > $end }

当使用了显式的界限函数,他可能通过返回任意真值来终止它的列表。因为序列操作符是列表结合性的,内部函数后面可以跟着 … ,然后另外一个函数来继续列表,等等。因此:

S03-sequence/misc.t lines 34–100

    1,   *+1   ... { $_ ==   9 },
    10,  *+10  ... { $_ ==  90 },
    100, *+100 ... { $_ == 900 }

产生:

    1,2,3,4,5,6,7,8,9,
    10,20,30,40,50,60,70,80,90,
    100,200,300,400,500,600,700,800,900

考虑到没有闭包的普通匹配规则,我们可以把上面的序列更简单地写为:

    1, 2, 3 ... 9,
    10, 20, 30 ... 90,
    100, 200, 300 ... 900

甚至仅仅是:

    1, 2, 3 ...
    10, 20, 30 ...
    100, 200, 300 ... 900

因为一个精确的匹配界限会被作为序列的一部分返回,所以提供的那个精确值是一个合适类型的值, 而非一个闭包。

对于左侧只有一个值的函数推断,最后的值被用于决定是 .succ 还是 .pred 更合适。 使用 cmp 来比较那两个值来决定前进的方向。

因此, 序列操作符能自动反转, 而范围操作符不会自动反转.

    'z' .. 'a'   # 表示一个空的范围
    'z' ... 'a'  # z y x ... a

你可以使用 ^... 形式来排除第一个值:

    'z' ^... 'a' # y x ... a
    5 ^... 1     # 4, 3, 2, 1

但是你要意识到, 如果列表的左侧很复杂, 特别是左侧是另一个序列时, 肯定会让你的读者困惑:

    1, 2, 3 ^... *;                  # 2, 3 ...  !
    1, 2, 3 ... 10, 20, 30 ^... *;   # 2, 3 ...  !?!?

是的, 对于极端喜欢对称性的那些人来说, 还有另外一种形式: ^...^

就像数字数值一样, 字符串匹配必须是精确的, 否则会产生无限序列.

注意下面这个序列:

    1.0, *+0.2 ... 2.0

是使用 Rat 算术计算的, 而不是 Num, 所以 2.0 精确地匹配了并结束了序列.

注意:只有在期望为一个 term 的地方,... 才会被识别为 yada 操作符。序列操作符只用于期望中缀操作符出现的地方。

如果你在 ... 前面放置了一个逗号,那么 ...会被识别为 yada 列表操作符 - 表达当列表到达那点时会失败的要求。

1..20, ... "I only know up to 20 so far mister"
> 1..20, fail "I only know up to 20 so far mister"  # I only know up to 20 so far mister

序列的末端是一个代表单个代码点的字符串时,会抛出一个特殊的异常, 因为典型地,用户会把这样一个字符串看作是字符而非字符串。如果你像这样说:

S03-sequence/nonnumeric.t lines 36–101

    'A' ... 'z'
    "xff" ... "