How do you do letcc in Clojure?

In The Seasoned Schemer , the author writes the following code:

(define intersectall (lambda (lset) (letcc hop (letrec ((A (lambda (lset) (cond ((null? (car lset)) (hop (quote ()))) ((null? (cdr lset)) (car lset)) (else (intersect (car lset) (A (cdr lset)))))))) (cond ((null? lset) (quote ())) (else (A lset))))))) 

Here's potentially what it might look like in Clojure:

 (defmacro letcc [name & body] `(letfn [(~name [arg#] (throw (ex-info (str '~name) {:name '~name :value arg#})))] (try ~@body (catch clojure.lang.ExceptionInfo e# (if (= '~name (:name (ex-data e#))) (:value (ex-data e#)) (throw e#)))))) (defn intersectall [lset] (letcc hop (letfn [(A [lset] (cond (empty? (first lset)) (hop ()) (empty? (rest lset)) (first lset) :else (intersect (first lset) (A (rest lset)))))] (cond (empty? lset) () :else (A lset))))) 

My question is: How do you do letcc in Clojure?

+6
source share
2 answers

Background

The core of Clojure does not support top-notch sequels. This and the fact that the JVM does not provide a way to capture the current continuation, means that there is no way to implement letcc that is satisfactory for all situations.

However, in some situations, you can implement the continuation. In particular, if you have all the code (that is, the code in which you must write the continuations), you can use the continuation-passing style (CPS). Basically, you add an additional parameter for each function. This parameter is a function that represents a continuation of this call. You "return" the value by calling the continue function. Of course, this style is a pain to write on its own - but, fortunately, it is a transformation that we can easily apply to specific code using macros.

CPS itself is not suitable for platforms that do not perform tail call optimization (TCO). Since the last step of any function in CPS is to call another function, without TCO, the stack overflows quickly, with the exception of the most trivial calculations. This problem can be solved by using thunking and trampolining.

Decision

As I mentioned above, you can write your own CPS conversion using macros. However, I invite you to check out my pulley.cps library, which already does this for you. There are alternatives, but as far as I know, pulley.cps is the only Clojure library that provides all of the following features:

  • call-cc / let-cc
  • Seamless calls between native (not converted) and converted code
  • Exception ( try / catch / finally ) support
  • binding (they are also correctly recursive!)
  • Allows you to provide a CPS version of an existing native function (this is necessary if you want to keep the continuation in this function)

Alternatives include:

  • delimc provides a library for delimited sequels. This does not seem very complete (for example, binding fails because it does not understand the try / finally block) and was not affected after 4 years.
  • algo.monads is a monad library for Clojure. There is a strong and interesting connection between monads and sequels, and algo.monads is a continuation of the monad. Although the monadic style is not entirely clear, it has the advantage of making the effect more explicit, which can help in encapsulating code that uses control effects from code that does not. In addition, the do notation (for example, the domonad macro) greatly blurs the lines between the direct and monadic styles.
+5
source

The continuation captured (letcc hop ...) in your example is used as an "upward continuation". Instead, one could use the name return : (letcc return ... (return () ...) . When a continuation with the name return called, the entire letcc expression evaluates to return , which is then returned as a result of intersectall .

This means that 1. the continuation continues (we return) and 2. the continuation is used only once. When these conditions are met, letcc can be implemented in terms of try and catch , as you did.

So, as I see it, by writing the letcc macro, you answered your question.

Now that Nathan Davis mentions that there are other options for using continuations, but Clojure does not support them directly.

Note. There is a related question here: The Seasoned Schemer, letcc and guile

+3
source

All Articles