我正在尝试使用reduce函数一步计算整数数组的平均值。 我不能这样做:
say (reduce {($^a + $^b)}, <1 2 3>) / <1 2 3>.elems;
因为它将平均值计算分成了两个部分。
我需要像这样做:
say reduce {($^a + $^b) / .elems}, <1 2 3>;
但是当然这样行不通。
如何一步完成?(使用 map 或其他函数都可以。)
我正在尝试使用reduce函数一步计算整数数组的平均值。 我不能这样做:
say (reduce {($^a + $^b)}, <1 2 3>) / <1 2 3>.elems;
因为它将平均值计算分成了两个部分。
我需要像这样做:
say reduce {($^a + $^b) / .elems}, <1 2 3>;
但是当然这样行不通。
如何一步完成?(使用 map 或其他函数都可以。)
TL;DR 这篇回答首先介绍了一种等效代码的习惯用法,然后讨论了P6风格的“暗示”编程和增加简洁性。 我还添加了关于Håkon ++在您问题的第一个评论中使用的超级运算符的“额外”脚注。5
我们将从一个简单的解决方案开始。1
P6有内置例程2可以实现你所要求的功能。这是使用内置的sub
的方法:
say { sum($_) / elems($_) }(<1 2 3>); # 2
下面是使用相应的{{3}}方法
:
say { .sum / .elems }(<1 2 3>); # 2
首先,让我们将.sum
替换为显式的归约:
.reduce(&[+]) / .elems
当在P6中的表达式开头使用&
时,您知道该表达式将first class citizen作为一个Callable
引用。
将中缀+
运算符称为函数值的一种冗长的方式是&infix:<+>
。简写方式是&[+]
。
如您所知,reduce
例程将二元操作作为参数,并将其应用于值列表。在方法形式(invocant.reduce
)中,"invocant"是列表。
上述代码调用两个没有显式调用者的方法——.reduce
和.elems
。这是一种"tacit" programming的形式;以这种方式编写的方法隐含地(或"tacitly")使用$_(也称为“the topic”或简称“it”)作为它们的调用者。
given
关键字将一个值绑定到$_
(也称为"it")用于单个语句或块。
(这就是given
的全部作用。许多其他关键字也可以将其置于顶层,但它们还会执行其他操作。例如,for
将一系列值绑定到$_
,而不仅仅是一个。)
因此,您可以编写:
say .reduce(&[+]) / .elems given <1 2 3>; # 2
或者:
$_ = <1 2 3>;
say .reduce(&[+]) / .elems; # 2
但是,考虑到你的重点是函数式编程,还有另外一种方法你应该知道。
首先,将代码包装在一个代码块中:
{ .reduce(&[+]) / .elems }
Block
,因此是一个lambda表达式。没有签名的lambda表达式会获得一个默认的signature,它接受一个可选参数。given
,例如:say do { .reduce(&[+]) / .elems } given <1 2 3>; # 2
但我们也可以使用普通的函数调用语法:
say { .reduce(&[+]) / .elems }(<1 2 3>)
(...)
调用了左侧的 Callable
,且在上述情况下将一个参数传递给期望一个参数的块中的括号,结果与前一行代码中的 do
4 和 given
相同。
以下是另一种编写方式:
<1 2 3>.&{.sum/.elems}.say; #2
这将会像调用方法一样调用一个代码块。如果你已经掌握了P6的基础知识,那么这仍然非常易读。
或者你可以开始变得有点儿滑稽:
<1 2 3>.&{.sum/$_}.say; #2
如果你懂P6的话,这段文字还是可读的。 /
是一个数字(除法)运算符。数字运算符会强制将其操作数转换为数字。在上面的例子中,$_
绑定到一个列表 <1 2 3>
。在Perl中,集合在数字上下文中的值是其元素数量。
到目前为止,我一直坚持使用标准P6。
当然,你可以编写任何Unicode字母命名的sub
或method
。如果你想要sum
和elems
的单个字母别名,那就去吧:
my (&s, &e) = &sum, &elems;
但是您也可以根据需要扩展或更改语言。例如,您可以创建用户定义的运算符:
#| LHS ⊛ RHS.
#| LHS is an arbitrary list of input values.
#| RHS is a list of reducer function, then functions to be reduced.
sub infix:<⊛> (@lhs, *@rhs (&reducer, *@fns where *.all ~~ Callable)) {
reduce &reducer, @fns».(@lhs)
}
say <1 2 3> ⊛ (&[/], &sum, &elems); # 2
我现在不打算解释这个。 (请在评论中随意提问。)我的重点只是强调您可以引入任意(前缀,中缀,环绕等)运算符。
如果自定义运算符不足够,您可以更改任何其他语法。 cf "braid"。
1 这就是我通常编写代码以执行问题所要求的计算的方式。 @timotimo++的评论促使我改变了我的演示方式,先从那里开始,然后转向专注于更FPish的解决方案。
2 在P6中,所有内置函数都称为“例程”,并且是Routine
的子类的实例 - 通常是Sub
或Method
。
3 并非所有内置的sub
例程都有相应命名的method
例程。反之亦然。相反,有时候存在相应命名的例程,但它们的工作方式并不完全相同(最常见的区别是sub
的第一个参数是否与方法形式中的“调用者”相同)。此外,您可以使用语法.&foo
(对于命名的Sub
)或.&{ ... }
(对于匿名的Block
)像调用方法一样调用子例程,或者使用语法foo invocant:
或foo invocant: arg2, arg3
(如果它具有超出调用者的参数)以类似于子例程调用的方式调用方法foo
。
4 如果块被用在显然应该调用它的地方,那么它会被调用。如果它没有被调用,则可以使用显式的do
语句前缀来调用它。
is nodal
trait)以及特定元素是否为Iterable
。8通过使用超级操作,开发者声明没有有意义的副作用这一假设是正确的。编译器将按照此假设进行操作,但不会检查其是否为真。从安全角度来看,这就像带有条件的循环。即使结果是无限循环,编译器也会遵循开发者的指示。
.sum
这个方法,它比 .reduce(&[+])
更加简短,也许更加清晰易懂。 - timotimogiven
关键字就是这个技巧的关键。因为它允许在一步中完成计算 :) - Terry.elems
;given @foo { ([+] $_) / $_ }
;可以正常工作(如果我们可以假设它已定义,则使用 with
也可以)。 - user0721090601
given <1 2 3> { say (reduce {($^a + $^b)}, $_ >>/>> $_.elems) };
- Håkon Hægland>>/>>
的使用)需要一些解释。 - Terry