Functional programming Perl 6使用reduce一次计算int数组的平均值

Functional programming Perl 6使用reduce一次计算int数组的平均值,functional-programming,raku,Functional Programming,Raku,我试图一步计算使用reduce函数的整数数组的平均值。我不能这样做: say (reduce {($^a + $^b)}, <1 2 3>) / <1 2 3>.elems; say(reduce{($^a+$^b)},)/.elems; 因为它在两个单独的块中计算平均值 我需要这样做: say reduce {($^a + $^b) / .elems}, <1 2 3>; 说reduce{($^a+$^b)/.elems}; 但它当然不起作用 如何一

我试图一步计算使用reduce函数的整数数组的平均值。我不能这样做:

say (reduce {($^a + $^b)}, <1 2 3>) / <1 2 3>.elems;
say(reduce{($^a+$^b)},)/.elems;
因为它在两个单独的块中计算平均值

我需要这样做:

say reduce {($^a + $^b) / .elems}, <1 2 3>;
说reduce{($^a+$^b)/.elems};
但它当然不起作用

如何一步到位?(欢迎使用map或其他功能。)

以下是使用and的示例:

给定{say([+]$\}/$\.elems};

TL;DR在讨论P6风格的“默契”编程和增加简洁性之前,这个答案从编写等效代码的惯用方法开始。我还添加了关于超级操作Håkon++的“额外”脚注,该操作在他们对您的问题的第一次评论中使用。5

也许不是您想要的,而是一个初始的惯用解决方案 我们将从一个简单的解决方案开始。1

P6内置了按您要求执行的例程2。下面是一种使用内置
sub
s执行此操作的方法:

say { sum($_) / elems($_) }(<1 2 3>); # 2
“函数式编程”呢? 首先,让我们用显式缩减替换
.sum

.reduce(&[+]) / .elems
当在P6中的表达式开头使用
&
时,您知道该表达式将
可调用的
称为

将中缀
+
运算符作为函数值引用的一种简便方法是
&infix:
。速记方式是
和[+]

您清楚地知道,
reduce
例程将二进制操作作为参数,并将其应用于值列表。在方法形式(
invocant.reduce
)中,是列表

上面的代码调用了两个方法--
.reduce
.elems
--它们没有显式的invocant。这是一种,;以这种方式编写的方法隐式(或)使用(又名“主题”或简称“它”)作为它们的invocent

主题化(明确确定“it”是什么)
given
为单个语句或块将单个值绑定到
$(也称为“it”)

(这就是给定的
所做的一切。许多其他关键字也会主题化,但也会做其他事情。例如,
For
将一系列值绑定到
$\uu
,而不仅仅是一个。)

因此你可以写:

say .reduce(&[+]) / .elems given <1 2 3>; # 2
上面是一个,因此是一个lambda。不带签名的lambda获得接受一个可选参数的默认值

现在我们可以再次使用给定的
,例如:

say do { .reduce(&[+]) / .elems } given <1 2 3>; # 2
这将调用一个块,就好像它是一个方法一样。在我看来,这仍然是非常可读的,特别是如果你知道P6的基础知识

或者你会开始变得愚蠢:

<1 2 3>.&{.sum/$_}.say; #2
但您也可以根据自己的需要扩展或更改语言。例如,可以创建用户定义的运算符:

#| 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
#| LHS⊛ RHS。
#|LHS是输入值的任意列表。
#|RHS是一个减缩函数列表,然后是要减缩的函数。
子中缀:(@lhs、*@rhs(&reducer、*@fns其中*.all~~Callable)){
减速器和减速器,@fns»。(@lhs)
}
说⊛ (&[/],&sum,&elems);#2.
我现在不想解释这个。(请随意在评论中提问。)我的观点只是强调您可以引入任意()运算符

如果自定义运算符不够,您可以更改。cf

脚注 1这是我通常编写代码进行问题中要求的计算的方式@timotimo++的评论促使我改变我的演示文稿,从那开始,然后才改变方向,专注于更具FPish的解决方案

2在P6中,所有内置函数都用通用术语“例程”来表示,并且是一个子类的实例,通常是or

3并非所有内置的
sub
例程都相应地命名为
method
例程。反之亦然。相反,有时会有相应命名的例程,但它们的工作方式并不完全相同(最常见的区别是
sub
的第一个参数是否与方法表单中的“invocant”相同),您可以调用一个子例程,就好像它是一个使用语法
&foo
的方法,用于命名的
Sub
&{…}
的匿名
,或者调用方法
foo
,看起来更像是使用语法
foo发票:
foo发票:arg2的子例程调用,arg3
如果其参数超出发票范围

4如果在显然应该调用的地方使用了块,那么它就是。如果未调用它,则可以使用显式的
do
语句前缀来调用它

5 Håkon对你的问题的第一次评论使用了“超级手术”。只需一个易于识别和记忆(对于一元操作)或一对(对于二元操作),就可以将一个操作分配给数据结构的所有“叶子”6(对于一元操作),或者根据一对数据结构的“叶子”配对创建一个新的操作(对于二元操作)。注意。超级操作是并行完成的

6超操作的“叶子”是什么,取决于所应用的操作的组合(请参阅)以及是否存在特定元素

7超操作是并行应用的,至少在语义上是这样。Hyperoperation假设“叶子”上的操作没有相互干扰的副作用——也就是说,将操作应用于一个“叶子”时,相对于将操作应用于任何其他“叶子”,任何副作用都可以安全地忽略


8通过使用超级操作,开发人员声明没有有意义的副作用的假设是正确的。编译器将根据它进行操作,但不会检查它是否为真。从安全角度讲,它就像一个有条件的循环。编译器将遵循开发人员的指令,即使结果是无限循环。

这里有一个示例:
给定{say(reduce{($^a+$^b)},$\u>/>>$\ uElems)}那工作
{ .reduce(&[+]) / .elems }
say do { .reduce(&[+]) / .elems } given <1 2 3>; # 2
say { .reduce(&[+]) / .elems }(<1 2 3>)
<1 2 3>.&{.sum/.elems}.say; #2
<1 2 3>.&{.sum/$_}.say; #2
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