Details
-
Type:
Improvement
-
Status:
Closed
-
Priority:
Major
-
Resolution: Won't Fix
-
Affects Version/s: 1.8-beta-4
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
-
Testcase included:yes
-
Patch Submitted:Yes
-
Number of attachments :
Description
problem
Closure#trampoline() is good feature but the API is little complicating IMHO.
One reason is Closure#trampoline() has following two meanings:
1) trampoline() is use to initiate trampoline call.
fact.trampoline().call(10000, 1G)
it can be
fact.trampoline()(10000, 1G)
this is same as the call of (trampoline ...) in Clojure.
2) trampoline() is use to return next call like continuation.
return fact.trampoline(n-1, total *1)
this is same as the return #() form in Clojure.
Those ware difficult to me before understand the logic. Both of those trampoline() return TrampolineClosure instances. So you have to understand the behavior of TrampolineClosure().
proposal
My proposal includes two modification to current Trampoline APIs:
- Introduce Closure#tcall(...) method for 1) meaning
- use Closure#call() and TrampolineClosure#call() for 2) meaning.
implementation
To replace Closure#call(), change the par-instance metaclass of the original closure instance. This MetaClass change is effective only during tcall() so you can reuse the closure for recursive call if you want.
sample code1
- current usage of trampoline
// 1-1 fact = { n, total -> if (n == 0) { total } else { fact.trampoline(n-1, total*n) } }.trampoline() fact(1000,1G)
or
// 1-2 fact = { n, total -> if (n == 0) { total } else { fact.trampoline(n-1, total*n) } } fact.trampoline().call(1000,1G)
- after applying this proposal
// 2-1 fact = { n, total -> if (n == 0) { total } else { fact(n-1, total*n) // this don't call fact recursively. // just returns TrampolineClosure. } } fact.tcall(1000,1G)
or
// 2-2 fact = { n, total -> if (n == 0) { total } else { call(n-1, total*n) // this don't call fact recursively. // just returns TrampolineClosure } } fact.tcall(1000,1G)
The point is, you don't have to modify the body of the original closure which using recursion to which you want to change it to use trampoline call.
The difference is only the method of call to the closure(change call() to tcall()).
And trampoline() and TrampolineClosure instance(returned from trampoline()) are both disappeared from the code.
tcall() make those stuff under the hood.
sample code2(mutual call)
- current usage of trampoline on mutual call
// 3-1 def oddp def evenp = { n -> if (n==0) { true } else { oddp.trampoline(n-1) } }.trampoline() oddp = { n-> if (n==0) { false } else { evenp.trampoline(n-1) } }.trampoline() assert evenp.call(1001) == (1001%2==0)
I fell there are too many trampoline() calls here
.
- after applying this proposal on mutual call
// 4-1 def oddp def evenp = { n -> if (n==0) { true } else { oddp(n-1) } } oddp = { n-> if (n==0) { false } else { evenp(n-1) } } assert evenp.tcall(1001) == (1001%2==0)
When using mutual call, what you have to do is the same as self recursion.
Just use tcall() instead of call() to closure to invoke.
To be exact, there is a small difference. In this code, evenp is converted to TrampolineClosure by tcall() but oddp is plain normal closure. But tail call is works well because evenp calls oddp as normal closure so consumes stack one level but oddp returns evenp()'s return value which is TrampolineClosure. So stack is not consumed anymore.
If you want to, by using trampoline() explicitly, you can do exactly same thing to 3-1:
// 4-2 def oddp def evenp = { n -> if (n==0) { true } else { oddp.trampoline(n-1) } } oddp = { n-> if (n==0) { false } else { evenp(n-1) } } assert evenp.tcall(1001) == (1001%2==0)