As far as I can tell, Clojure's recur is backed by the compiler whereas in other lisps it is implemented at a lower level.
As I read, this wouldn't be a "general" TCO. Aside from the obvious (a keyword + checking are needed), is in any way recur less powerful?
recur only supports tail-recursion optimization, which is a subclass of general TCO. Clojure also supports mutual or indirect recursion through trampoline.
EDIT
Also, I think general TCO was expected to land in JVM with Java 7 and recur was meant as a temporary solution. Then Oracle happened. I've mixed that with Project Lambda's (adding closures in Java) schedule
recur differs slightly from full TCO in that recur works with both loops and functions and does not do some of the things that a full implementation of TCO would. The philosophical backing for this is to make the special part look special as opposed to silently optimizing a uniform syntax.
Related
I tried using the y-combinator (in both Lua and Clojure) as I thought that would allow me to exceed the size of default stack implementations when using recursion. It seems I was mistaken. Yes, it works, but in both of these systems, the stack blows at exactly the same point as it does using plain old recursion. A lowish ~3600 in Clojure and a highish ~333000 on my Android Lua implementation. It is also a bit slower than regular recursion.
So is there anything to be gained by using the y-combinator, or is it just an intellectual exercise to prove a point? Have I missed something?
===
PS. Sorry, I should have made it clearer that I am aware I can use TCO to exceed the stack. My question does not relate to that. I am interested in this
a) from the academic/intellectual point of view
b) whether there is anything that can be done about those function that cannot be written tail recursively.
The Y combinator allows a non-recursive function to be used recursively, but that recursion still consumes stack space through nested function invocations.
For functions that can't be made tail-recursive, you could try refactoring them using continuation passing style, which would consume heap space instead of stack.
Here's a good overview of the topic: https://www.cs.cmu.edu/~15150/previous-semesters/2012-spring/resources/lectures/11.pdf
A “tail call” will allow you to exceed any stack size limitations. See Programming in Lua, section 6.3: Proper Tail Calls:
...after the tail call, the program does not need to keep any information about the calling function in the stack. Some language implementations, such as the Lua interpreter, take advantage of this fact and actually do not use any extra stack space when doing a tail call. We say that those implementations support proper tail calls.
If you haven't seen it yet, there is a good explanation here: What is a Y-combinator?
In summary, it helps to prove that the lambda calculus is Turing complete, but is useless for normal programming tasks.
As you are probably aware, in Clojure you would just use loop/recur to implement a loop that does not consume the stack.
Why does Clojure have the "recur" special form?
When I replace the "recur" with the function itself I get the same result:
(defn print-down-from [x]
(when (pos? x)
(println x)
(recur (dec x))
)
)
(print-down-from 5)
Has the same result as
(defn print-down-from [x]
(when (pos? x)
(println x)
(print-down-from (dec x))
)
)
(print-down-from 5)
I was wondering if "recur" is just a safety measure so that the compiler will throw an error if a developer happens to use a non-tail recursion. Or maybe it's necessary for the compiler to optimize the tail recursion?
But what I'm wondering most is about any other reasons other than stack consumption.
As explained in the clojure.org page on functional programming:
In the absence of mutable local variables, looping and iteration must take a different form than in languages with built-in for or while constructs that are controlled by changing state. In functional languages looping and iteration are replaced/implemented via recursive function calls. Many such languages guarantee that function calls made in tail position do not consume stack space, and thus recursive loops utilize constant space. Since Clojure uses the Java calling conventions, it cannot, and does not, make the same tail call optimization guarantees. Instead, it provides the recur special operator, which does constant-space recursive looping by rebinding and jumping to the nearest enclosing loop or function frame. While not as general as tail-call-optimization, it allows most of the same elegant constructs, and offers the advantage of checking that calls to recur can only happen in a tail position.
When you don't use recur (or trampoline), your function calls consume stack: Thus, if you ran (print-down-from 100000), you would quickly observe a crash.
Providing this language facility, then, offers several benefits:
Contrary to conventional recursive calls (as given in the question's example), using recur does not consume stack.
The author knows that TCO is in use (and stack is not consumed), as use in a position where TCO is not possible would cause a compile-time failure. As such, the stack-consumptions characteristics are obvious both to the code's author and its readers, unlike languages with only automatic TCO (where one would need to read carefully -- taking macro expansions into account -- to determine whether a call is genuinely in tail position before knowing whether it is optimized).
Compatibility with conventional JVM calling conventions, and thus native interoperability with code written in other JVM-centric languages is preserved.
Finally, for background, see one of several other questions on the topic on StackOverflow:
Automatic TCO in Clojure (asking whether an automatic facility to make explicit invocation of recur unnecessary is available).
Why does TCO require support from the VM? (asking for the basis behind the claim made in the above-quoted clojure.org documentation that automatic TCO cannot be implemented without either JVM support or breaking use of JVM calling conventions and thus harming interoperability).
Why can't tail calls be optimized in JVM-based Lisps? (likewise, more generally)
As I understand it, the loop .. recur uses tail-end recursion, so your program doesn't blow the stack, and regular recursion does not. Some solutions to problems on 4clojure wind up not using loop .. recur, because -- I'm taking an educated guess -- the solution can only be derived using a direct, recursive function call, instead of loop .. recur.
From what I have read, in some of the Clojure books from a few years ago, you should feel free to use loop .. recur.
However, at least from the discussion in those books I have read and from answers I have received to my Clojure questions here in SO, there is some general consensus to try to solve your problem first using constructs like map. If that is not possible, then by all means go ahead and use loop .. recur, and if you do not think there is a possibility of blowing your stack, a direct recursion call.
is Recursion is more general than Iteration?
for me Iteration means repetitive control using language constructs other than subroutine calls (like loop-constructs and/or explicit
goto’s), whereas recursion means as repetitive control obtained using subroutine calls). which is more general in these two?
Voted to close as likely to prompt opinion-based responses; my opinion-based response is: recursion is more general because:
it's simply a user-case of function calls, which a language will already have; and
recursion captures both a direct one-to-one pattern of repetition and more complicated patterns, such as tree traversal, divide and conquer, etc.
Conversely iteration tends to be a specific language-level construct allowing only a direct linear traversal.
In so far as it is not opinion-based, the most reasonable answer is that neither recursion nor iteration is more general than the other. A language can be Turing complete with recursion but no iteration (minimal Lisps are like that) and a language can also be Turing complete with iteration but no recursion (earlier versions of Fortran didn't support recursion). Both recursion and iteration are widely used. Iteration is probably more commonly used since for every person who learns programming with something like Lisp or Haskell there are probably a dozen who learn programming with things like Java or Visual Basic -- but I don't think that "most commonly used" is a good synonym for "general".
One of the properties of functional languages is that functions do not have side effects, thus the same input should always yield the same output. It seems that those languages could easily and heavily benefit from memoization.
But, at least regarding Erlang, there is no default memoization of the functions' calls. Is there any particular reason that Erlang (and other functional languages as far as I know) do memoize by default (or with a simple trigger), or at least have explicit, good support of memoization?
Is there something inherently wrong with memoization?
One reason I can imagine is that with memoization your memory footprint can grow quickly. Right, but Erlang already runs on VM and manages memory, so I guess it could tame caches and prevent them from growing quite easily.
Related:
Memoization in Erlang
How does one use cached data in a functional language such as Erlang?
Simple example for Erlang memoization
Edit:
What is an idempotent operation?
Too big too fail :)
There are a number of mistaken assumptions in this question.
One of the properties of functional languages is that functions do not have side effects
Incorrect, only "purely functional languages" have such a constraint. Erlang is not purely functional.
It permits arbitrary side effects in functions.
Is there any particular reason that Erlang (and other functional languages as far as I know) do memoize by default (or with a simple trigger), or at least have explicit, good support of memoization?
No languages (at least non-toy ones) implement by-default memoization of all function calls. Why? Massive space leaks would ensue.
I read somewhere where rich hickey said:
"I think continuations might be neat
in theory, but not in practice"
I am not familiar with clojure.
1. Does clojure have continuations?
2. If no, don't you need continuations? I have seen a lot of good examples especially from this guy. What is the alternative?
3. If yes, is there a documentation?
When talking about continuations, you’ll have to distinguish between two different kinds of them:
First-class continuations – Continuation-support that is deeply integrated in the language (Scheme or Ruby). Clojure does not support first-class continuations.
Continuation-passing-style (CPS) – CPS is just a style of coding and any language supporting anonymous functions will allow this style (which applies to Clojure too).
Examples:
-- Standard function
double :: Int -> Int
double x = 2 * x
-- CPS-function – We pass the continuation explicitly
doubleCPS :: Int -> (Int -> res) -> res
doubleCPS x cont = cont (2 * x)
; Call
print (double 2)
; Call CPS: Continue execution with specified anonymous function
double 2 (\res -> print res)
Read continuation on Wikipedia.
I don’t think that continuations are necessary for a good language, but especially first-class continuations and CPS in functional languages like Haskell can be quite useful (intelligent backtracking example).
I've written a Clojure port of cl-cont which adds continuations to Common Lisp.
https://github.com/swannodette/delimc
Abstract Continuations
Continuations are an abstract notion that are used to describe control flow semantics. In this sense, they both exist and don't exist (remember, they're abstract) in any language that offers control operators (as any Turing complete language must), in the same way that numbers both exist (as abstract entities) and don't exist (as tangible entities).
Continuations describe control effects such as function call/return, exception handling, and even gotos. A well founded language will, among other things, be designed with abstractions that are built on continuations (e.g., exceptions). (That is to say, a well-founded language will consist of control operators that were designed with continuations in mind. It is, of course, perfectly reasonable for a language to expose continuations as the only control abstraction, allowing users to build their own abstractions on top.)
First Class Continuations
If the notion of a continuation is reified as a first-class object in a language, then we have a tool upon which all kinds of control effects can be built. For example, if a language has first-class continuations, but not exceptions, we can construct exceptions on top of continuations.
Problems with First-Class Continuations
While first-class continuations are a powerful and useful tool in many cases, there are also some drawbacks to exposing them in a language:
Different abstractions built on top of continuations may result in unexpected / unintuitive behavior when composed. For example, a finally block might be skipped if I use a continuation to abort a computation.
If the current continuation may be requested at any time, then the language run-time must be structured so that it is possible to produce some data-structure representation of the current continuation at any time. This places some degree of burden on the run-time for a feature which, for better or worse, is often considered "exotic". If the language is hosted (such as Clojure is hosted on the JVM), then that representation must be able to fit within the framework provided by the hosting platform. There may also be other features a language would like to maintain (e.g., C interop) which restrict the solution space. Issues such as these increase the potential of an "impedence mismatch", and can severely complicate development of a performant solution.
Adding First-Class Continuations to a Language
Through metaprogramming, it is possible to add support for first-class continuations to a language. Generally, this approach involves transforming code to continuation-passing style (CPS), in which the current continuation is passed around as an explicit argument to each function.
For example, David Nolen's delimc library implements delimited continuations of portions of a Clojure program through a series of macro transforms. In a similar vein, I have authored pulley.cps, which is a macro compiler that transforms code into CPS, along with a run-time library to support more core Clojure features (such as exception handling) as well as interop with native Clojure code.
One issue with this approach is how you handle the boundary between native (Clojure) code and transformed (CPS) code. Specifically, since you can't capture the continuation of native code, you need to either disallow (or somehow restrict) interop with the base language or place a burden on the user of ensuring the context will allow any continuation they wish to capture to actually be captured.
pulley.cps tends towards the latter, although some attempts have been made to allow the user to manage this. For instance, it is possible to disallow CPS code to call into native code. In addition, a mechanism is provided to supply CPS versions of existing native functions.
In a language with a sufficiently strong type system (such as Haskell), it is possible to use the type system to encapsulate computations which might use control operations (i.e., continuations) from functionally pure code.
Summary
We now have the information necessary to directly answer your three questions:
Clojure does not support first-class continuations due to practical considerations.
All languages are built on continuations in the theoretical sense, but few languages expose continuations as first-class objects. However, it is possible to add continuations to any language via, e.g., transformation into CPS.
Check out the documentation for delimc and/or pulley.cps.
Is continuation a necessary feature in a language?
No. Plenty of languages don't have continuations.
If no, dont you need continuations? I have seen a lot of good examples especially from this guy. What is the alternative?
A call stack
A common use of continuations is in the implementation of control structures for: returning from a function, breaking from a loop, exception handling etc. Most languages (like Java, C++ etc) provide these features as part of the core language. Some languages don't (e.g: Scheme). Instead, these languages expose continuatiions as first class objects and let the programmer define new control structures. Thus Scheme should be looked upon as a programming language toolkit, not a complete language in itself.
In Clojure, we almost never need to use continuations directly, because almost all the control structures are provided by the language/VM combination. Still, first class continuations can be a powerful tool in the hands of the competent programmer. Especially in Scheme, continuations are better than the equivalent counterparts in other languages (like the setjmp/longjmp pair in C). This article has more details on this.
BTW, it will be interesting to know how Rich Hickey justifies his opinion about continuations. Any links for that?
Clojure (or rather clojure.contrib.monads) has a continuation monad; here's an article that describes its usage and motivation.
Well... Clojure's -> implements what you are after... But with a macro instead