Raku Syntax I Miss in Other Languages
— 焉知非鱼Raku Syntax I Miss in Other Languages
Self-describing code
Junctions #
- Distributive
%hash{any(@keys)}
等价于:
any(%hash{@keys})
- Boolean
类型 | 操作符 | True if … |
---|---|---|
any | | | 至少一个值为真 |
all | & | 所有值都为真 |
one | ^ | 只有一个值为真 |
none | 值都不为真 |
Junctions 通常出现在布尔上下文中。例如, 在下面的例子中, $value
和几个值进行相等性比较。很容易写出这样的代码:
if $value == 1 || $value == 2 || $value == 5
使用 any
Junction 会简洁不少:
if $value == any(1, 2, 5)
if $value == (1,2,4).any
惯用法是使用 |
操作符号来进行多值比较:
if $value == 1|2|5
找出数组中满足条件的第一个元素, 我们首先想到的可能是, 使用 for
循环迭代数组, 找出满足条件的元素就立即退出循环:
my $result = False;
for @values -> $value {
if $value > 42 {
$result = True;
last;
}
}
if $result { ... }
改用 Junction 后等价于:
if any(@values) > 42 { ... }
还可以在 Junction 上调用方法或运算符:
if one(@values).is-prime { ... }
if all(@values) %% 3 { ... }
Named arguments(命名参数) #
Colonpair(冒号对儿) 通常用于命名参数中:
:foo(42)
foo => 42
sub bar(:$foo) {
say $foo;
}
bar(:foo(42));
有几种特殊形式的冒号对儿:
:foo
:foo(True)
:!foo
:foo(False)
:foo<bar>
:foo("bar")
:foo
与 foo => True
相同, :!foo
等价于 foo => False
。:foo<bar>
使用了一组尖括号引起了值, 值在尖括号中不进行插值。
来看一个命名参数例子, 下面的代码遍历 @dirs
中的目录, 找出后缀名为 txt 且不为空的文件:
for find(@dirs, :file, :ext<txt>, :!empty) -> $file {
...
}
冒号对儿的值可以是变量, 但是在圆括号中再写一遍变量名就显得啰嗦:
foo(:bar($bar), :baz($baz), :quz($quz))
因此, 冒号对儿提供了一种简写形式, 如果冒号后面紧跟着 $
、@
、%
和 &
等符号, 那么冒号对儿的值就是 $sth
、@sth
、%sth
和 &sth
:
foo(:$bar, :$baz, :$quz)
这种简写形式消除了命名参数的重复。
Pointy blocks(尖号块儿) #
All blocks are Callable, 即所有的块儿都是可调用的。
for
blocks
-> $elem { ...}
就是尖号块儿:
for @array -> $elem {
...
}
for
循环依次把 @array
中的每个元素赋值给尖号块儿中的 $elem
变量, 然后执行尖号块儿的主体。
- Ordering
如果 foo
例程有返回值且不为假, 则赋值给 $value
, 然后执行块儿的主体:
if my $value = foo() { ... }
但是上面的语句在 Raku 中是不合法的, 要使用尖号块儿的方式:
if foo() -> $value { ... }
Signatures(签名) #
for
循环可以一次迭代两个(或多个)元素。尖号块儿相当于匿名函数, 其中的 $first, $second
就是尖号块儿的签名。
for @array -> $first, $second {
...
}
下面的智能匹配中, 变量 $1
和 $2
有些多余:
if / (\S+) \s+ (.*) / {
my $name = $1;
my $value = $2;
...
}
通过尖号块儿, 把匹配结果直接赋值给 $name
和 $value
, 节省了两个变量名:
if / (\S+) \s+ (.*) / -> ($name, $value) {
...
}
for
尖号块儿和 if
尖号块儿的结构类似, 语法上非常整齐:
for expression() -> $value { ... }
if expression() -> $value { ... }
Whatever code #
如果 grep 的过滤条件中有多个变量, 那么使用尖号块儿这种匿名函数比较合适:
@numbers.grep(-> $n { $n > 2 });
如果过滤条件中只有一个变量, 那么形式更短的 Whatever code 更符合惯用法:
@numbers.grep(* > 2);
Meta-operators #
- Reduction
- Zip
- Corss
- Hyper
Reduction meta operators
- fold/reduce an infix operator
- Respects associativity
reduce
运算符可以用于求和:
my $sum = reduce { $^a + $^b }, @list;
my $sum = [+] @list
[+] # sum
[*] # product
[~] # join
[===] # all equal
[<>] # ascending order
[||] # first true value, if any
Zip 元运算符用于连接列表:
(1, 2, 3) Z+ (30, 20 ,10)
# (21, 22, 13)
-> ($a, $b)
解构 Zip 后的元素:
for @a Z @b -> ($a, $b) {
...
}
Z=>
运算符通常用于从两个列表中制作哈希:
%hash = @keys Z=> @values
Zip 元运算符可以写成链式的:
@a Z @b Z @c
[Z] @list-of-lists
Cross 是交叉运算符。使用两层 for 循环也可以实现交叉运算符的功能, 就是代码稍长:
gather for 3, 9 -> $i {
for 2, 10 -> $j {
take $i + $j
}
}
而使用交叉运算符, 一行代码搞定:
3, 9 X+ 2, 10
添加前缀也很简单:
"prefix-" X~ @list
Hyper 运算符可以把任何运算符(中缀、前缀和后缀等)应用到列表上:
@list».abs
@list.map(*.abs)
!«@list
@list.map(!*)
@list»[1]
@list.map(*[1])
欧几里得距离:
@a Z- @b
Squared(平方)
(@a Z- @b)»²
Summed(求和)
[+] (@a Z- @b)»²
Square root(求平方根)
sqrt [+] (@a Z- @b)»²
Smartmatch(智能匹配) #
“Is the value part of this set”
@list.grep(* > 2)
@list.grep({ $_ eq "foo" })
@list.grep(Int)
@list.grep({ .isa(Innt) })
@list.grep(/foo .* bar/)
@list.grep({ .match(/foo .* bar/) })
@list.grep(2..4)
@list.grep({ 2 <= $_ <= 4 })
@list.grep(:is-prime)
@list.grep({ .is-prime })
combine junctions(结合 Junctions):
@list.grep(:is-prime & /22/)
@list.grep({ .is-prime && .matches(/22/) })
@list.grep(none(/22/))
@list.grep({ !.matches(/22/) })
最后, 还是查找文件的例子:
find(@dirs,
:ext('rakumo'|'pm6'),
:size(* > 1024),
:depth(3..5),
:contains(/raku/)
);