Wait the light to fall

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")

:foofoo => 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/)
);

原文链接: https://www.youtube.com/watch?v=elalwvfmYgk