Continuation Passing Style and Macros in Clojure - Jan 2012

80
Continuation-passing style and Macros with Clojure Leonardo Borges @leonardo_borges http://www.leonardoborges.com http://www.thoughtworks.com

description

Presentation at the Sydney Clojure User Group

Transcript of Continuation Passing Style and Macros in Clojure - Jan 2012

Page 1: Continuation Passing Style and Macros in Clojure - Jan 2012

Continuation-passing style and Macros with Clojure

Leonardo Borges@leonardo_borgeshttp://www.leonardoborges.comhttp://www.thoughtworks.com

Page 2: Continuation Passing Style and Macros in Clojure - Jan 2012

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)

Page 3: Continuation Passing Style and Macros in Clojure - Jan 2012

CPS - Pythagorean theoremYou know the drill... a² + b² = c²

;;direct style(defn pyth [a b] (+ (* a a) (* b b)))

Page 4: Continuation Passing Style and Macros in Clojure - Jan 2012

CPS - Pythagorean theoremYou 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))))))

Page 5: Continuation Passing Style and Macros in Clojure - Jan 2012

WTF?!

Page 6: Continuation Passing Style and Macros in Clojure - Jan 2012

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))))))

Page 7: Continuation Passing Style and Macros in Clojure - Jan 2012

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

Page 8: Continuation Passing Style and Macros in Clojure - Jan 2012

CPS - Fibonacci;;direct style(defn fib [n] (if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))

Page 9: Continuation Passing Style and Macros in Clojure - Jan 2012

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))))

(fib-cps 20 identity);55

Page 10: Continuation Passing Style and Macros in Clojure - Jan 2012

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

Page 11: Continuation Passing Style and Macros in Clojure - Jan 2012

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))))

(fib-cps 20 identity);55

accept function

Page 12: Continuation Passing Style and Macros in Clojure - Jan 2012

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))))

(fib-cps 20 identity);55

return continuation

Page 13: Continuation Passing Style and Macros in Clojure - Jan 2012

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))))

(fib-cps 20 identity);55

next continuation

Page 14: Continuation Passing Style and Macros in Clojure - Jan 2012

CPS - generic function builders

(defn mk-cps [accept? end-value kend kont] (fn [n] ((fn [n k] (let [cont (fn [v] (k (kont v n)))] (if (accept? n) (k end-value) (recur (dec n) cont)))) n kend)))

Page 15: Continuation Passing Style and Macros in Clojure - Jan 2012

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

Page 16: Continuation Passing Style and Macros in Clojure - Jan 2012

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

Page 17: Continuation Passing Style and Macros in Clojure - Jan 2012

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

Page 18: Continuation Passing Style and Macros in Clojure - Jan 2012

Seaside - Task example [1]

go" [ self chooseCheese." self confirmCheese ] whileFalse." self informCheese

[1] Try it yourself (http://bit.ly/seaside-task)

Page 19: Continuation Passing Style and Macros in Clojure - Jan 2012

Seaside - Task example

chooseCheese" cheese := self" " chooseFrom: #( 'Greyerzer' 'Tilsiter' 'Sbrinz' )" " caption: 'What''s your favorite Cheese?'." cheese isNil ifTrue: [ self chooseCheese ]

confirmCheese" ^ self confirm: 'Is ' , cheese , 'your favorite Cheese?'

informCheese" self inform: 'Your favorite is ' , cheese , '.'

Page 20: Continuation Passing Style and Macros in Clojure - Jan 2012

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)

Page 21: Continuation Passing Style and Macros in Clojure - Jan 2012

Macros

If you give someone Fortran, he has Fortran.If you give someone Lisp, he has any language he pleases.

- Guy Steele

Page 22: Continuation Passing Style and Macros in Clojure - Jan 2012

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

Page 23: Continuation Passing Style and Macros in Clojure - Jan 2012

e.g.: avoiding nesting levels(def guitar {:model "EC-401FM" :brand "ESP" :specs { :pickups {:neck {:brand "EMG" :model "EMG 60"} :bridge {:brand "EMG" :model "EMG 81"}} :body "Mahoganny" :neck "Mahoganny"}})

Page 24: Continuation Passing Style and Macros in Clojure - Jan 2012

e.g.: avoiding nesting levels(def guitar {:model "EC-401FM" :brand "ESP" :specs { :pickups {:neck {:brand "EMG" :model "EMG 60"} :bridge {:brand "EMG" :model "EMG 81"}} :body "Mahoganny" :neck "Mahoganny"}})

(:model (:neck (:pickups (:specs guitar))))

Page 25: Continuation Passing Style and Macros in Clojure - Jan 2012

e.g.: avoiding nesting levels

what if we could achieve the same like this instead?

(t guitar :specs :pickups :neck :model)

Page 26: Continuation Passing Style and Macros in Clojure - Jan 2012

Macros to the rescue!

(defmacro t ([v form] (if (seq? form) `(~(first form) ~v ~@(rest form)) (list form v))) ([v form & rest] `(t (t ~v ~form) ~@rest)))

Page 27: Continuation Passing Style and Macros in Clojure - Jan 2012

Macros to the rescue!

(defmacro t ([v form] (if (seq? form) `(~(first form) ~v ~@(rest form)) (list form v))) ([v form & rest] `(t (t ~v ~form) ~@rest)))

(t guitar :specs :pickups :neck :model)

Page 28: Continuation Passing Style and Macros in Clojure - Jan 2012

What’s with all that `~@ ?

Page 29: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

Page 30: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

(def my-list (1 2 3))

Page 31: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

(def my-list (1 2 3)) ;java.lang.Integer cannot be cast to clojure.lang.IFn

Page 32: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

(def my-list (1 2 3)) ;java.lang.Integer cannot be cast to clojure.lang.IFn

(def my-list '(1 2 3)) ;Success!

Page 33: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

(def my-list (1 2 3)) ;java.lang.Integer cannot be cast to clojure.lang.IFn

(def my-list '(1 2 3)) ;Success!

'my-list ;my-list

Page 34: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

(def my-list (1 2 3)) ;java.lang.Integer cannot be cast to clojure.lang.IFn

(def my-list '(1 2 3)) ;Success!

'my-list ;my-list'(1 2 3) ;(1 2 3)

Page 35: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

(def my-list (1 2 3)) ;java.lang.Integer cannot be cast to clojure.lang.IFn

(def my-list '(1 2 3)) ;Success!

'my-list ;my-list'(1 2 3) ;(1 2 3)

Syntax-quote: automatically qualifies all unqualified symbols

Page 36: Continuation Passing Style and Macros in Clojure - Jan 2012

Quoting Prevents evaluation

(def my-list (1 2 3)) ;java.lang.Integer cannot be cast to clojure.lang.IFn

(def my-list '(1 2 3)) ;Success!

'my-list ;my-list'(1 2 3) ;(1 2 3)

Syntax-quote: automatically qualifies all unqualified symbols

`my-list ;user/my-list

Page 37: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote

Evaluates some forms in a quoted expression

Page 38: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote

Evaluates some forms in a quoted expression

Before unquoting...

Page 39: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote

Evaluates some forms in a quoted expression

Before unquoting...`(map even? my-list)

Page 40: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote

Evaluates some forms in a quoted expression

Before unquoting...`(map even? my-list);;(clojure.core/map clojure.core/even? user/my-list)

Page 41: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote

Evaluates some forms in a quoted expression

Before unquoting...`(map even? my-list);;(clojure.core/map clojure.core/even? user/my-list)

After...

Page 42: Continuation Passing Style and Macros in Clojure - Jan 2012

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)

Page 43: Continuation Passing Style and Macros in Clojure - Jan 2012

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)))

Page 44: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks the sequence at hand

Page 45: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks the sequence at hand

Before unquote-splicing...

Page 46: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks the sequence at hand

Before unquote-splicing...`(+ ~my-list)

Page 47: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks the sequence at hand

Before unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))

Page 48: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks the sequence at hand

Before unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))

(eval `(+ ~my-list))

Page 49: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks 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

Page 50: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks 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...

Page 51: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks 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)

Page 52: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks 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)

Page 53: Continuation Passing Style and Macros in Clojure - Jan 2012

Unquote-splicingUnpacks 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

Page 54: Continuation Passing Style and Macros in Clojure - Jan 2012

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)))

Page 55: Continuation Passing Style and Macros in Clojure - Jan 2012

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?

Page 56: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion

Page 57: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

Page 58: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

;;expands to

Page 59: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

;;expands to(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))

Page 60: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

;;expands to(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))

(require '[clojure.walk :as walk])

Page 61: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

;;expands to(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))

(require '[clojure.walk :as walk])(walk/macroexpand-all '(t guitar :specs :pickups :neck :model))

Page 62: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

;;expands to(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))

(require '[clojure.walk :as walk])(walk/macroexpand-all '(t guitar :specs :pickups :neck :model))

;;expands to

Page 63: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

;;expands to(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))

(require '[clojure.walk :as walk])(walk/macroexpand-all '(t guitar :specs :pickups :neck :model))

;;expands to(:model (:neck (:pickups (:specs guitar))))

Page 64: Continuation Passing Style and Macros in Clojure - Jan 2012

Macro expansion(macroexpand '(t guitar :specs :pickups :neck :model))

;;expands to(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))

(require '[clojure.walk :as walk])(walk/macroexpand-all '(t guitar :specs :pickups :neck :model))

;;expands to(:model (:neck (:pickups (:specs guitar))))

However our macro is worthless. Clojure implements this for us, in the form of the -> [4] macro

[4] The -> macro on ClojureDocs (http://bit.ly/yCyrHL)

Page 65: Continuation Passing Style and Macros in Clojure - Jan 2012

Implementing unless

We want...

(unless (zero? 2) (print "Not zero!"))

Page 66: Continuation Passing Style and Macros in Clojure - Jan 2012

1st try - function

Page 67: Continuation Passing Style and Macros in Clojure - Jan 2012

1st try - function(defn unless [predicate body] (when (not predicate) body))

Page 68: Continuation Passing Style and Macros in Clojure - Jan 2012

1st try - function(defn unless [predicate body] (when (not predicate) body))

(unless (zero? 2) (print "Not zero!"));;Not zero!

Page 69: Continuation Passing Style and Macros in Clojure - Jan 2012

1st try - function

(unless (zero? 0) (print "Not zero!"));;Not zero!

(defn unless [predicate body] (when (not predicate) body))

(unless (zero? 2) (print "Not zero!"));;Not zero!

Page 70: Continuation Passing Style and Macros in Clojure - Jan 2012

1st try - function

(unless (zero? 0) (print "Not zero!"));;Not zero!

(defn unless [predicate body] (when (not predicate) body))

(unless (zero? 2) (print "Not zero!"));;Not zero!

Oh noes!

Page 71: Continuation Passing Style and Macros in Clojure - Jan 2012

1st try - function

Function arguments are eagerly evaluated!

Page 72: Continuation Passing Style and Macros in Clojure - Jan 2012

Unless - our second macro

(defmacro unless [predicate body] `(when (not ~predicate) ~@body))

Page 73: Continuation Passing Style and Macros in Clojure - Jan 2012

Unless - our second macro

(defmacro unless [predicate body] `(when (not ~predicate) ~@body))

(macroexpand '(unless (zero? 2)

Page 74: Continuation Passing Style and Macros in Clojure - Jan 2012

Unless - our second macro

(defmacro unless [predicate body] `(when (not ~predicate) ~@body))

(macroexpand '(unless (zero? 2) (print "Not zero!")))

Page 75: Continuation Passing Style and Macros in Clojure - Jan 2012

Unless - our second macro

(defmacro unless [predicate body] `(when (not ~predicate) ~@body))

(macroexpand '(unless (zero? 2) (print "Not zero!")))

;;expands to

Page 76: Continuation Passing Style and Macros in Clojure - Jan 2012

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))

Page 77: Continuation Passing Style and Macros in Clojure - Jan 2012

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!")))

Page 78: Continuation Passing Style and Macros in Clojure - Jan 2012

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)

Page 79: Continuation Passing Style and Macros in Clojure - Jan 2012

Thanks!

Questions?!Leonardo Borges

@leonardo_borgeshttp://www.leonardoborges.comhttp://www.thoughtworks.com

Page 80: Continuation Passing Style and Macros in Clojure - Jan 2012

References

• The Joy of Clojure (http://bit.ly/AAj760)• Automatically RESTful Web Applications (http://bit.ly/ydltH6)• Seaside (http://www.seaside.st)• http://en.wikipedia.org/wiki/Continuation-passing_style• http://matt.might.net/articles/by-example-continuation-passing-style/• http://en.wikipedia.org/wiki/Static_single_assignment_form

Leonardo Borges@leonardo_borgeshttp://www.leonardoborges.comhttp://www.thoughtworks.com