In the book 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 is potentially how it could look 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?
The core Clojure language does not support first-class continuations. That, and the fact that the JVM does not provide a way to capture the current continuation, means there is no way of implementing letcc
that is satisfactory for all situations.
However, it is possible to implement continuations in some situations. Specifically, if you own all the code (that is, the code in which you must capture continuations) then you can employ continuation-passing-style (CPS). Basically, you add an extra parameter to each function. This parameter is a function that represents the continuation of that call. You "return" a value by calling the continuation function. Of course, this style is a pain to write by itself -- but fortunately this is a transform we can easily apply to specific code via macros.
By itself, CPS is unsuitable for platforms that do not do tail-call optimization (TCO). Because the last step of any function in CPS is to invoke another function, without TCO the stack quickly overflows except for the most trivial of computations. This problem can be solved by employing thunking and trampolining.
As I alluded above, you can write your own CPS transform using macros. However, I would invite you to checkout my pulley.cps library, which already does this for you. There are alternatives, but as far as I'm aware pulley.cps is the only Clojure library that provides all of the following:
call-cc
/let-cc
try
/catch
/finally
) supportbinding
forms (they're properly tail-recursive too!)Alternatives include:
binding
fails because it doesn't understand the try
/finally
block) and hasn't been touched in 4 years.do
notation (e.g., the domonad
macro) greatly blurs the lines between direct and monadic style.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With