Wait the light to fall

Whatever Star

焉知非鱼

Whatever 是什么? #

Placeholder for unspecified value/parameter - 未指定的值/参数的占位符。

* 字面量在 「term」 位置上创建 「Whatever」 对象。 * 的大部分魔法来自于 「Whatever 柯里化」. 当 * 作为 item 与很多操作符组合使用时, 编译器会把表达式转换为 「WhateverCode」 类型的闭包.

my $c = * + 2;          # same as   -> $x { $x + 2 };
say $c(4);              # 6

如果一个表达式中有 N 个 *, 则会产生一个含有 N 个参数的闭包:

my $c = * + *;          # same as   -> $x, $y { $x + $y }

在复杂的表达式中使用 * 也会产生闭包:

my $c = 4 * * + 5;      # same as   -> $x { 4 * $x + 5 }

* 号身上调用方法也会产生闭包:

<a b c>.map: *.uc;      # same as <a b c>.map: -> $char { $char.uc }

前面提到, 不是所有的操作符和语法都会把 * 柯里化为 「WhateverCode」。 下面这几种情况, * 仍旧是 「Whatever 对象」。

例外               Example    What it does

逗号               1,*,2      用一个 * 元素生成一个 Parcel
范围操作符         1..*       Range.new(:from(1), :to(*));
序列操作符         1 ... *    无限列表
智能匹配           1 ~~ *     返回 True
赋值               $x = *     把 * 赋值给 $x
绑定               $x := *    把 * 绑定给 $x
列表重复           1 xx *     生成无限列表

范围操作符被特殊处理. 它们不使用 Whatever-Stars 柯里化, 但是它们使用 「WhateverCode」 进行柯里化.

say (1..*).WHAT;        # Range
say (1..*-1).WHAT;      # WhateverCode

上面的 *-1 是作为参数传递了。

下面这些也能使用:

.say for 1..*;          # infinite loop
my @a = 1..4;
say @a[0..*];           # 1 2 3 4
say @a[0..*-2];         # 1 2 3

因为 Whatever-currying 是纯粹的语法编译器转换, 这不会在运行时把存储的 Whatever-stars 柯里化为 WhateverCodes.

my $x = *;
$x + 2;                 # not a closure, dies because
                        # it can't coerce $x to Numeric

存储 Whatever-stars 的使用案例像上面提到的那样, 但要把柯里化异常的情况也包含进去. 例如, 如果你想要一个默认的无限序列:

my $max    = potential-upper-limit() // *;
my $series = known-lower-limit() ... $max;

一个存储后的 * 会在智能匹配的特殊情况下生成 WhateverCode. 注意, 正被柯里化的并非真正储存的 *, 而是在 LHS 上的 *

my $constraint = find-constraint() // *;
my $maybe-always-matcher = * ~~ $constraint;

如果这个假定的 find-constraint 没有找到约束, 则 maybe-always-matcher 会对任何东西都返回 True.

$maybe-always-matcher(555);      # True
$maybe-always-matcher(Any);      # True

Whatever Star #

当作为一个「项」使用时, 我们把 * 叫做 “Whatever”。当不是实际值时,它用作占位符。例如, 1, 2, 3 ... *,意思是没有终结点的自然数序列。

Whatever 闭包 #

Whatever 最强大的用处是 「Whatever」 闭包。

对于 Whatever 没有特殊意义的普通操作符:把 Whatever 当作参数传递时就创建了一个闭包! 所以,举个例子:

* + 1 # 等价于 -> $a { $a + 1 }
* + * # 等价于 -> $a, $b { $a + $b }

一个星号占一个坑儿。

@list.grep(* > 10)                  # 返回 @list 数组中所有大于 10 的元素
@list.grep( -> $ele { $ele > 10 } ) # 同上, 使用显式的闭包
@list.grep: -> $ele { $ele > 10 }   # 同上, 使用冒号调用方式
@list.grep: * > 10                  # 同上
@list.grep: { $_ > 10 }             # 同上


@list.map(* + *)                    # 返回 @list 数组中每两个元素的和
@list.map( -> $a, $b { $a+$b } )    # 同上, 使用显式的闭包

如果给 @a[ ] 的方括号里面传递一个闭包, 它会把 @a 数组的元素个数作为参数传递并计算!

数组的最后一个元素

my @a =  1,22,33,11;
say @a[*-1];
say @a[->$a {$a-1}]; # $a  即为数组@a 的元素个数

数组的倒数第二个元素

say @a[*-2];
say @a[->$a {$a-2}];

所以 @a[*/2]@a 数组的中间元素, @a[1..*-2]@a 中不包含首尾元素的其它元素。 1, 1, * + * ... * 是一个无限列表, * + * 是后来值的生成规则, 最后一个 * 表示没有终结测试。

把闭包存储在标量中

my $a = -> $b { $b + 1 }
$a(3) # 4

my @add_by_one = @list.map($a); # 对 @list 中的每个元素加 1

Raku 的列表求值是惰性的,只要你不要求最后一个元素, 无限列表是没问题的。使用绑定 (:=) 操作符把列表赋值给变量:

my @fib := 1, 1, * + * ... *

如果我稍后要 @fib[40] 的值, 会生成足够多的元素以获取数组的第 41 个元素,那些生成的元素会被记忆。尽管未来, 如果列表未绑定给变量, 之前的值会被忘记, 大部分 Raku 列表函数能作用并生成惰性列表。

@a.map@a.grep 都生成「惰性列表」, 即使 @a 不是惰性的。 @fib.grep(* %% 2) 是一个偶数惰性列表,例如 @fib Z @a 生成一个惰性列表: @fib[0], @a[0], @fib[1], @a[1] ...。 给 for 循环传递一个无限列表是没问题的, 它会循环直到停止。

但是要注意不能使用嵌套的闭包:

my @a = 1 .. 10;
my @b = @a.map: { * ** 2 }
===SORRY!=== Error while compiling:
Malformed double closure; WhateverCode is already a closure without curlies,
so either remove the curlies or use valid parameter syntax instead of *
at line 2 ------> <BOL><HERE><EOL>

注意上面的错误信息, 说的已经很明显了, WhateverCode 已经是一个不带花括号的闭包了, 所以要么移除花括号, 要么使用合法的参数语法代替 * 号, 提示信息足够清楚了。所以, 按照提示:

方法一:使用 $_ 代替 *

> my @b = @a.map: { $_ ** 2 }
[1 4 9 16 25 36 49 64 81 100]

方法二:

> my @b = @a.map:  * ** 2
[1 4 9 16 25 36 49 64 81 100]

方法三, 显式的使用 cloure:

> my @b = @a.map: -> $item { $item ** 2 }
[1 4 9 16 25 36 49 64 81 100]