Iterable
接口使用以下签名定义了一个名为fold()
的方法:
Result fold<Result>(Result initial,
Result accumulating(Result partial, Element elem))
其中Element
是Iterable
的元素类型。 此方法接受一个初始值和一个累加器函数,该函数又依次应用于可迭代对象的每个元素。 例如:
Integer sum = (1..10).fold(0, plus<Integer>);
有时,我们不需要初始值,因为可以从第一个元素开始累加。 遵循Scala和F#使用的约定,让我们将此函数称为reduce()
。 然后,我们希望能够编写:
Integer sum = (1..10).reduce(plus<Integer>);
但是这种方法的签名应该是什么? 第一个刺可能会给我们:
Element reduce(Element accumulating(Element partial, Element elem))
但是此签名比应有的限制更多。 将reduce()
的结果类型作为元素类型的超类型是完全合理的。 Scala使用下限类型约束来处理此问题。 使用虚构的下限语法将其译为锡兰,它看起来像:
Result reduce<Result>(Result accumulating(Result partial, Element elem))
given Result abstracts Element
在此,下限约束确保将第一个元素分配给累加器函数的第一个参数。 但是Ceylon没有下界类型约束。 为什么? 好吧,因为似乎实际上我们几乎可以使用联合类型来实现相同的效果。 因此,让我们尝试:
Result|Element reduce<Result>(
Result accumulating(Result|Element partial, Element elem))
现在,让我们尝试实现此签名。 一种可能性是:
Result|Element reduce<Result>(
Result accumulating(Result|Element partial, Element elem)) {
assert (!empty, is Element initial = first);
variable Result|Element partial = initial;
for (elem in rest) {
partial = accumulating(partial, elem);
}
return partial;
}
断言将处理Iterable
为空的情况,如果iterable对象没有第一个元素,则会导致AssertionException
。
另外,在Iterable
为空的情况下,我们可能更希望返回null
,这建议以下实现:
Result|Element|Null reduce<Result>(
Result accumulating(Result|Element partial, Element elem)) {
if (!empty, is Element initial = first) {
variable Result|Element partial = initial;
for (elem in rest) {
partial = accumulating(partial, elem);
}
return partial;
}
else {
return null;
}
}
回到Scala,我们注意到Scala具有reduce()
两个版本,它们与我们刚刚看到的两种可能性完全相似。 第一个版本在空情况下引发异常,第二个版本reduceOption()
返回包装器类Option
的实例。
但是在锡兰,我们可以做得更好。 在锡兰, Iterable
具有一个看上去有点神秘的第二类型参数,名为Absent
, given Absent satisfies Null
的上限将given Absent satisfies Null
。 我们通常写为{T*}
的Iterable<T,Null>
是一个可能为空的可迭代对象。 我们通常写为{T+}
的Iterable<T,Nothing>
是一个我们知道为非空的可迭代对象。
因此,我们得出了reduce()
的以下定义:
Result|Element|Absent reduce<Result>(
Result accumulating(Result|Element partial, Element elem)) {
value initial = first;
if (!empty, is Element initial) {
variable Result|Element partial = initial;
for (elem in rest) {
partial = accumulating(partial, elem);
}
return partial;
}
else {
return initial;
}
}
现在,对于“跨越”范围表达式像1..n
,这是非空的,我们得到一个非空的返回类型:
Integer sum = (1..n).reduce(plus<Integer>);
另一方面,对于像1:n
这样的“分段”范围表达式,可能是空的,我们得到一个可选的返回类型:
Integer? sum = (1:n).reduce(plus<Integer>);
最棒的是,它永远不会引发异常。 这是我谦卑地提交的“该死的尼斯”。
在这里注意工会类型为我们做了多少工作。 与Scala的reduce()
/ reduceOption()
,它们让我们消除了:
- 下界类型约束,
- 第二个有效重载的方法版本,以及
- 包装器
Option
类。
我已经将reduce()
定义添加到Iterable
,它将在下一版Ceylon中可用。
翻译自: https://www.javacodegeeks.com/2014/01/the-signature-of-reduce-in-ceylon.html