New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Continuation Passing Style and Macros in Clojure - Jan 2012
1. Continuation-passing style and
Macros with Clojure
Leonardo Borges
@leonardo_borges
http://www.leonardoborges.com
http://www.thoughtworks.com
2. CPS in a nutshell
• not new. It was coined in 1975 by Gerald Sussman and Guy Steele
• style of programming where control is passed explicitly in the form of a
continuation
• every function receives an extra argument k - the continuation
• makes implicit things explicit, such as order of evaluation, procedure
returns, intermediate values...
• used by functional language compilers as an intermediate representation
(e.g.: Scheme, ML, Haskell)
3. CPS - Pythagorean theorem
You know the drill...
a² + b² = c²
;;direct style
(defn pyth [a b]
(+ (* a a) (* b b)))
4. CPS - Pythagorean theorem
You know the drill...
a² + b² = c²
;;direct style
(defn pyth [a b]
(+ (* a a) (* b b)))
;;CPS
(defn pyth-cps [a b k]
(*-cps a a (fn [a2]
(*-cps b b (fn [b2]
(+-cps a2 b2 k))))))
6. Untangling pyth-cps
;;CPS
(defn *-cps [x y k]
(k (* x y)))
(defn +-cps [x y k]
(k (+ x y)))
(defn pyth-cps [a b k]
(*-cps a a (fn [a2]
(*-cps b b (fn [b2]
(+-cps a2 b2 k))))))
7. Untangling pyth-cps
;;CPS
(defn *-cps [x y k]
(k (* x y)))
(defn +-cps [x y k]
(k (+ x y)))
(defn pyth-cps [a b k]
(*-cps a a (fn [a2]
(*-cps b b (fn [b2]
(+-cps a2 b2 k))))))
(pyth-cps 5 6 identity) ;61
10. Another look at CPS
Think of it in terms of up to three functions:
• accept: decides when the computation should end
• return continuation: wraps the return value
• next continuation: provides the next step of the computation
11. CPS - Fibonacci
;;CPS
(defn fib-cps [n k]
(letfn [(cont [n1]
(fib-cps (- n 2) (fn [n2]
(k (+ n1 n2)))))]
(if (<= n 1)
(k n)
(recur (- n 1) cont)))) accept function
(fib-cps 20 identity);55
15. CPS - generic function
builders
;;Factorial
(def fac (mk-cps zero? 1 identity #(* %1 %2)))
(fac 10); 3628800
;;Triangular number
(def tri (mk-cps zero? 1 dec #(+ %1 %2)))
(tri 10); 55
16. Seaside - a more practical use
of CPS
• continuation-based web application framework for Smalltalk
• UI is built as a tree of independent, stateful components
• uses continuations to model multiple independent flows between
different components
17. Seaside - a more practical use
of CPS
• continuation-based web application framework for Smalltalk
• UI is built as a tree of independent, stateful components
• uses continuations to model multiple independent flows between
different components
• memory intensive
• not RESTful by default
18. Seaside - Task example [1]
go
" [ self chooseCheese.
" self confirmCheese ] whileFalse.
" self informCheese
[1] Try it yourself (http://bit.ly/seaside-task)
20. CPS - Other real world
usages
• web interactions ~ continuation invocation [2]
• event machine + fibers in the Ruby world [3]
• functional language compilers
• ajax requests in javascript - callbacks anyone?
• node.js - traditionally blocking functions take a callback instead
• ...
[2] Automatically RESTful Web Applications (http://bit.ly/ydltH6)
[3] Untangling Evented Code with Ruby Fibers (http://bit.ly/xm0t51)
21. Macros
If you give someone Fortran, he has Fortran.
If you give someone Lisp, he has any language he pleases.
- Guy Steele
22. Macros
• Data is code is data
• Programs that write programs
• Magic happens at compile time
• Most control structures in Clojure are built out of macros
40. Unquote
Evaluates some forms in a quoted expression
Before unquoting...
`(map even? my-list)
;;(clojure.core/map clojure.core/even? user/my-list)
41. Unquote
Evaluates some forms in a quoted expression
Before unquoting...
`(map even? my-list)
;;(clojure.core/map clojure.core/even? user/my-list)
After...
42. Unquote
Evaluates some forms in a quoted expression
Before unquoting...
`(map even? my-list)
;;(clojure.core/map clojure.core/even? user/my-list)
After...
`(map even? '~my-list)
43. Unquote
Evaluates some forms in a quoted expression
Before unquoting...
`(map even? my-list)
;;(clojure.core/map clojure.core/even? user/my-list)
After...
`(map even? '~my-list)
;;(clojure.core/map clojure.core/even? (quote (1 2 3)))
49. Unquote-splicing
Unpacks the sequence at hand
Before unquote-splicing...
`(+ ~my-list)
;;(clojure.core/+ (1 2 3))
(eval `(+ ~my-list))
;;java.lang.Integer cannot be cast to clojure.lang.IFn
50. Unquote-splicing
Unpacks the sequence at hand
Before unquote-splicing...
`(+ ~my-list)
;;(clojure.core/+ (1 2 3))
(eval `(+ ~my-list))
;;java.lang.Integer cannot be cast to clojure.lang.IFn
After...
51. Unquote-splicing
Unpacks the sequence at hand
Before unquote-splicing...
`(+ ~my-list)
;;(clojure.core/+ (1 2 3))
(eval `(+ ~my-list))
;;java.lang.Integer cannot be cast to clojure.lang.IFn
After...
`(+ ~@my-list)
52. Unquote-splicing
Unpacks the sequence at hand
Before unquote-splicing...
`(+ ~my-list)
;;(clojure.core/+ (1 2 3))
(eval `(+ ~my-list))
;;java.lang.Integer cannot be cast to clojure.lang.IFn
After...
`(+ ~@my-list)
;;(clojure.core/+ 1 2 3)
53. Unquote-splicing
Unpacks the sequence at hand
Before unquote-splicing...
`(+ ~my-list)
;;(clojure.core/+ (1 2 3))
(eval `(+ ~my-list))
;;java.lang.Integer cannot be cast to clojure.lang.IFn
After...
`(+ ~@my-list)
;;(clojure.core/+ 1 2 3)
(eval `(+ ~@my-list)) ;6
54. back to our macro...
(defmacro t
([v form] (if (seq? form)
`(~(first form) ~v ~@(rest form))
(list form v)))
([v form & rest] `(t (t ~v ~form) ~@rest)))
55. back to our macro...
(defmacro t
([v form] (if (seq? form)
`(~(first form) ~v ~@(rest form))
(list form v)))
([v form & rest] `(t (t ~v ~form) ~@rest)))
better now?
78. Unless - our second macro
(defmacro unless [predicate body]
`(when (not ~predicate)
~@body))
(macroexpand '(unless (zero? 2)
(print "Not zero!")))
;;expands to
(if (clojure.core/not (zero? 2))
(do (print "Not zero!")))
You could of course use the if-not [5] macro to the
same effect
[5] The if-not macro on ClojureDocs (http://bit.ly/yOIk3W)