这里对Groovy闭包的进阶操作进行介绍
Composition组合
Groovy支持对多个闭包进行组合。特别地, 闭包还重载了左移<<、右移>>运算符,。使得非常方便实现将一个闭包的输出作为另一个闭包的输入,示例如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class CompositionDemo { static void main(String[] args) { def fx = {x -> x+2} def gx = {x -> x*3}
def h1x = {x -> fx( gx(x) )} assert h1x(3) == 11
def h2x = {x -> gx( fx(x) )} assert h2x(3) == 15
def y1x = gx << fx assert y1x(3) == 15
def y2x = fx >> gx assert y2x(3) == 15 } }
|
Currying柯里化
具体到Groovy当中,其闭包的柯里化可以实现对原来接收N个参数的闭包,仅确定其中的部分参数,并使之返回一个新的闭包。在新闭包中仅包含未确定的参数。典型地,Groovy支持左柯里化、右柯里化、指定参数位置的柯里化等多种形式。示例如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class CurryingDemo { static void main(String[] args) { Closure closure5 = { x,y, z -> "$x--$y~~$z" } Closure closure6 = closure5.curry("Aaron") assert closure6.call("Bob", "Amy") == "Aaron--Bob~~Amy"
Closure closure7 = closure5.curry("China", "USA") assert closure7.call("UK") == "China--USA~~UK"
Closure closure8 = closure5.rcurry("Z1") assert closure8.call("X1", "Y1") == "X1--Y1~~Z1"
Closure closure9 = closure5.rcurry("Y2", "Z2") assert closure9.call("X2") == "X2--Y2~~Z2"
Closure c1 = { a,b,c,d -> "$a--$b~~$c>>$d" }
Closure c2 = c1.ncurry(1,"Hello") assert c2.call("0", "2", "3") == "0--Hello~~2>>3"
Closure c3 = c1.ncurry(1,"Hello", "Aaron") assert c3.call("0", "3") == "0--Hello~~Aaron>>3" } }
|
Memoization记忆化
在递归过程中,如果记住某些重复计算的结果,则可以大大减少计算量。典型地,通过递归的形式计算斐波那契数列的第N项时,其会产生多次重复的计算调用过程。这个时候我们在Groovy闭包中就可以引入Memoization记忆化这一特性,缓存已经计算过的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class MemoizationDemo { static void test1() { def count = 0 def fib1 fib1 = { long n -> count++ if( n==0 ) { return 0 } else if( n==1 ) { return 1 } else { return fib1(n-1) + fib1(n-2) } } assert fib1(6) == 8 assert count == 25
count = 0 def fib3 fib3 = { long n -> count++ if( n==0 ) { return 0 } else if( n==1 ) { return 1 } else { return fib3(n-1) + fib3(n-2) } }.memoize() assert fib3(6) == 8 assert count == 7 } }
|
进一步地,还可以通过有参版本的memoize方法实现对缓存结果数量的限制。包括最多、最少以及指定范围数的缓存数。示例代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class MemoizationDemo { static void test2() { def f1 f1 = { n -> if(n==1) { return 1 } return f1(n-1) + n }.memoizeAtMost(3);
def f2 f2 = { n -> if(n==1) { return 1 } return f2(n-1) + n }.memoizeAtLeast(3);
def f3 f3 = { n -> if(n==1) { return 1 } return f3(n-1) + n }.memoizeBetween(3,5); } }
|
Trampoline蹦床
对于递归而言,其最大风险就在于由于递归过深导致的SOF栈溢出。在Groovy中不论是普通递归还是尾递归,均存在上述风险。示例代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class TrampolineDemo {
static void test1() { def fact1 fact1 = { n -> if(n<2) { return 1 } return fact1(n-1) * n }
fact1(2000) }
static void test2() { def fact2 fact2 = { n, result=1 -> if(n<2) { return result } return fact2(n-1, result*n) }
fact2(2000) } }
|
通过test2的代码,我们实际上可以发现Groovy并未对尾递归形式作进一步优化,使得其依然存在SOF栈溢出的风险。但在Groovy中则可对尾递归应用trampoline,使得Groovy能够对尾递归的调用进行优化,避免发生SOF。这实际上也是我们使用尾递归写法的真正意义所在。具体地。一方面,在递归闭包时通过trampoline方法传参;另一方面,在定义闭包过程中,则通过无参版本的trampoline方法将闭包包装为TrampolineClosure。示例如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class TrampolineDemo {
static void test3() { def fact3 fact3 = { n, result=1 -> if(n<2) { return result } return fact3.trampoline(n-1, result*n) }.trampoline()
fact3(2000) } }
|
参考文献
- Groovy In Action · 2nd Edition Dierk König、Guillaume Laforge著