3 ms·
The JVM precludes general tail-call elimination though.
by whateveracct 3y ago
The JVM precludes general tail-call elimination though.
- thmorriss 3y agothe clojure loop construct is often cleaner than code written to be tail recursive
- uyrifo 3y agoAnd often faster: https://medium.com/hackernoon/faster-clojure-reduce-57a104448ea4 https://medium.com/hackernoon/faster-clojure-reduce-57a10444... Yet it’s always noted as code smell associated with “inexperienced candidates” in interviews. For that matter, first and last too: https://medium.com/hackernoon/faster-clojure-reduce-57a104448ea4 https://medium.com/hackernoon/faster-clojure-reduce-57a10444... The amount of paired programmers suggesting changing nths to firsts and lasts is demoralizing.
- jordibc 3y agoIt does preclude it, but clojure found an arguably elegant solution to it, using recur[1] instead. As a plus, in addition to achieving the same result as tail-call elimination, it does check that the call is indeed in tail position, and also works together with loop[2]. For me, it made me not miss tail-call elimination at all. [1] https://clojuredocs.org/clojure.core/recur https://clojuredocs.org/clojure.core/recur [2] https://clojuredocs.org/clojure.core/loop https://clojuredocs.org/clojure.core/loop
- packetlost 3y agoIt is, IMO, a missed opportunity to use a hard-coded identifier for `recur`ing instead of the `(let sym ((...)) ...)` form that would let you nest loops. Aside from that, I agree. Tail-call optimization's benefits are wildly overblown.
- whateveracct 3y agoThe benefits aren't overblown if you are someone who learned Lisp with a functional approach. As in, using higher-order functions etc. You have to be careful whenever you approach a problem that way on the JVM.
- packetlost 3y agoCan you provide an example?
- whateveracct 3y agohttps://news.ycombinator.com/item?id=39261659 https://news.ycombinator.com/item?id=39261659
- xdavidliu 3y agowhat does tail-call optimization have to do with higher-order functions? I thought the former pertains to iterative procedures written with recursive syntax, where the recursive call is at the very end of the function and called by itself, so stack size is O(1). Higher-order functions means passing functions to things like map, filter, etc.
- throwaway17_17 3y agoIn the context of higher order functions, tail call elimination allows for the avoidance of building up intermediate stack frames and the associated calling costs of functions when doing things like composing functions, particularly when calling large chains of nested function calls. The benefits of TCO for something like mapping a function can also be pretty large because the recursive map can be turned into a while loop as you describe at the beginning of your comment. The optimization of stack frame elision is pretty large for function calls on the JVM and the stack limits are not very amenable to ‘typical’ higher order function ‘functional programming’ style.
- 3y ago
- whateveracct 3y agoThe issue arises when you program really heavily with closures and function composition. You sadly cannot do functional programming as in "programming with functions" without care on the JVM.
- lispm 3y agoIn Scheme: (define (foo) (bar)) the call to bar is a tail call. How does recur optimize this? Well, it doesn't, since "general TCO" / "full TCO" means that any tail call gets optimized, such that the stack does not grow. Clojure recur/loop is just a loop notation. Looping construct in most languages provides a simple conversion of self recursion (a function calls itself recursively) to a loop: update the loop variables and then do the next loop iteration. But the general case of tail calls is not covered by a simple local loop, like what is provided by Clojure.