5 ms·
Clojure does actually support TCO, it doesn't support automatic TCO because of the JVM limitations. And after using explicit TCO calls in clojure for a while I
by retrogradeorbit 10y ago
Clojure does actually support TCO, it doesn't support automatic TCO because of the JVM limitations. And after using explicit TCO calls in clojure for a while I actually prefer it now over automatic TCO. Because I can clearly see from reading my code where tail calls are optimized, and where they build up stack. I don't have to try and work out if the recursive call is in the tail position. It's all explicit.
For mutually recursive TCO there's `trampoline`
https://clojuredocs.org/clojure.core/trampoline https://clojuredocs.org/clojure.core/trampoline
- nemoniac 10y agoAgreed that explicit is better than implicit but I can't imagine when you might want tail calls to build up stack. And if you do have a use case for it in mind, wouldn't you want to be explicit about that and have TCO be the default?
- retrogradeorbit 10y agoWell that (default being TCO, and explicitly declaring a stack call) would mean every normal function call (the vast majority) would need to be explicitly declared (call func args...), and the implicit case would need to blow up everywhere it's invoked in a non-tail position. What I was getting at is: in an automatic TCO language you don't actually know if the call is optimised or not. You might think it's optimised, but it isn't, because it's not in the tail position. You don't find this out until one day in production the stack explodes. The only way to know if it's optimised is to determine if it is in the tail position. That sometimes is straight forwards, but sometimes requires careful thought. It is certainly not always obvious. Additionally, another coder can come along and break your tail position. You've gone "return func()" and it's TCO and then later someone changes it to "return 1 + func()" and now the call to func is not TCO, but there is no warning and no obvious outward signs that this has changed. In the explicit TCO of clojure, if the second programmer adds the extension the compiler will explode with something like "recur not in tail position", immediately informing you that this has happened, and then you may either refactor to put it back in the tail position, or change it to a full stack function call once you determine that building up stack is ok in this case. Now I think loop/recur was implemented in Clojure as a work around the JVM limitations, but in doing so I think Hickey stumbled onto a really great new syntactic formulation for TCO. Explicitly declared TCO. I like it.
- paulddraper 10y agoThis is why I like Scala's approach so much. It will TCO automatically. If you want to make sure you haven't made a mistake, simply add the @tailrec annotation, and the compiler will give you an error if it can't TCO.
- retrogradeorbit 10y agoThat sounds pretty good! I will check that out.
- groovy2shoes 10y agoClojure's loop/recur is pretty much a degenerate version of Scheme's named let, which has been part of Scheme since long before Clojure was a thing: (define (sum xs) (let loop ((acc 0) (xs xs)) (if (null? xs) acc (loop (+ acc (car xs)) (cdr xs))))) It's often used in Scheme for iteration, including cases where you would otherwise need to define a helper function to implement an iterative (tail-recursive) version of a function, as I did with sum above. The named let construct is more general than loop/recur, since you can supply a name and properly nest the forms.
- didibus 10y agoYou probably wouldn't ever want to not optimize, but the point is that, by being explicit, you know what parts are and which are not. If you think about it, you can't really do the opposite, because it is not possible to TCO every recursive calls.
- lmm 10y agoI want useful stack traces by default - the cases where I most need a stack trace are usually precisely the cases where I didn't think much about that function when writing it.
- Chyzwar 10y agoTCO is possible in JVM but not in Java. Clojure is just Java library and share all Java problems like TCO, Class sizes or inline limits. I no longer believe in transpiration or hosted languages. Proper language have LLVM backend. Look at Rust, Swift, Crystal, this is future.
- deleted 10y ago[deleted]
- di4na 10y agoOr you use a VM that was build for that cough cough erlang cough cough
- Chyzwar 10y agoBut Clojure and Scala choose to sell theirs souls for Java popularity.
- jeletonskelly 10y agoIs "selling their souls" really what they're doing? When writing a new language shouldn't one consider its adoption by the community to be a key part of its success? The purist would say all that matters is sticking to a core philosophy while the pragmatist is willing to make some concessions for long-term viability and adoption so long as it doesn't completely undermine the purpose of the language. Essentially, don't you think Hickey, after several failed attempts at writing new programming languages, had given quite a bit of thought about what makes a language successful (and useful) before making the decision to make Clojure JVM based? Of course he did; even explaining the factors that went into the decision. That's not "selling your soul", it's a practical decision that comes from a lifetime of experience.
- Chyzwar 10y agoBecause Clojure is not new language it is another Lisp. Language adoption should not be primary driver. Clojure is more Java based than JVM based. This is main issue. It inherits all Java limitations and share problems inherited from Lisp world. https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentVector.java https://github.com/clojure/clojure/blob/master/src/jvm/cloju... By selling soul I mean: 1. Clojure will forever have ugly/broken stacktraces. 2. Inline/Class limits on JVM will not disappear. 3. Half of Clojure libraries are just wrappers. 4. Lack of TCO and other recursions optimisation. 5. Performance will be always worse than Java 6. Type system with garbage in -> garbage out approach