Continuation Passing Style and Macros in Clojure - Jan 2012

3,716 views

Published on

Presentation at the Sydney Clojure User Group

Published in: Technology

Continuation Passing Style and Macros in Clojure - Jan 2012

  1. 1. Continuation-passing style and Macros with Clojure Leonardo Borges @leonardo_borges http://www.leonardoborges.com http://www.thoughtworks.com
  2. 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 acontinuation• every function receives an extra argument k - the continuation• makes implicit things explicit, such as order of evaluation, procedurereturns, intermediate values...• used by functional language compilers as an intermediate representation(e.g.: Scheme, ML, Haskell)
  3. 3. CPS - Pythagorean theorem You know the drill... a² + b² = c² ;;direct style (defn pyth [a b] (+ (* a a) (* b b)))
  4. 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))))))
  5. 5. WTF?!
  6. 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. 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
  8. 8. CPS - Fibonacci;;direct style(defn fib [n] (if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))
  9. 9. 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
  10. 10. Another look at CPSThink 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. 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
  12. 12. 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) return continuation (recur (- n 1) cont))))(fib-cps 20 identity);55
  13. 13. CPS - Fibonacci;;CPS(defn fib-cps [n k] next continuation (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
  14. 14. 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)))
  15. 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. 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. 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. 18. Seaside - Task example [1] go " [ self chooseCheese. " self confirmCheese ] whileFalse. " self informCheese [1] Try it yourself (http://bit.ly/seaside-task)
  19. 19. Seaside - Task examplechooseCheese" cheese := self" " chooseFrom: #( Greyerzer Tilsiter Sbrinz )" " caption: Whats your favorite Cheese?." cheese isNil ifTrue: [ self chooseCheese ]confirmCheese" ^ self confirm: Is , cheese , your favorite Cheese?informCheese" self inform: Your favorite is , cheese , .
  20. 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. 21. MacrosIf you give someone Fortran, he has Fortran.If you give someone Lisp, he has any language he pleases. - Guy Steele
  22. 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
  23. 23. 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"}})
  24. 24. 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))))
  25. 25. e.g.: avoiding nesting levels what if we could achieve the same like this instead? (t guitar :specs :pickups :neck :model)
  26. 26. 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)))
  27. 27. 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)
  28. 28. What’s with all that `~@ ?
  29. 29. QuotingPrevents evaluation
  30. 30. QuotingPrevents evaluation(def my-list (1 2 3))
  31. 31. QuotingPrevents evaluation(def my-list (1 2 3));java.lang.Integer cannot be cast to clojure.lang.IFn
  32. 32. QuotingPrevents evaluation(def my-list (1 2 3));java.lang.Integer cannot be cast to clojure.lang.IFn(def my-list (1 2 3)) ;Success!
  33. 33. QuotingPrevents 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
  34. 34. QuotingPrevents 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)
  35. 35. QuotingPrevents 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
  36. 36. QuotingPrevents 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
  37. 37. UnquoteEvaluates some forms in a quoted expression
  38. 38. UnquoteEvaluates some forms in a quoted expressionBefore unquoting...
  39. 39. UnquoteEvaluates some forms in a quoted expressionBefore unquoting...`(map even? my-list)
  40. 40. UnquoteEvaluates some forms in a quoted expressionBefore unquoting...`(map even? my-list);;(clojure.core/map clojure.core/even? user/my-list)
  41. 41. UnquoteEvaluates some forms in a quoted expressionBefore unquoting...`(map even? my-list);;(clojure.core/map clojure.core/even? user/my-list)After...
  42. 42. UnquoteEvaluates some forms in a quoted expressionBefore unquoting...`(map even? my-list);;(clojure.core/map clojure.core/even? user/my-list)After...`(map even? ~my-list)
  43. 43. UnquoteEvaluates some forms in a quoted expressionBefore 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)))
  44. 44. Unquote-splicingUnpacks the sequence at hand
  45. 45. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...
  46. 46. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list)
  47. 47. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))
  48. 48. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))(eval `(+ ~my-list))
  49. 49. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))(eval `(+ ~my-list));;java.lang.Integer cannot be cast to clojure.lang.IFn
  50. 50. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))(eval `(+ ~my-list));;java.lang.Integer cannot be cast to clojure.lang.IFnAfter...
  51. 51. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))(eval `(+ ~my-list));;java.lang.Integer cannot be cast to clojure.lang.IFnAfter...`(+ ~@my-list)
  52. 52. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))(eval `(+ ~my-list));;java.lang.Integer cannot be cast to clojure.lang.IFnAfter...`(+ ~@my-list);;(clojure.core/+ 1 2 3)
  53. 53. Unquote-splicingUnpacks the sequence at handBefore unquote-splicing...`(+ ~my-list);;(clojure.core/+ (1 2 3))(eval `(+ ~my-list));;java.lang.Integer cannot be cast to clojure.lang.IFnAfter...`(+ ~@my-list);;(clojure.core/+ 1 2 3)(eval `(+ ~@my-list)) ;6
  54. 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. 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?
  56. 56. Macro expansion
  57. 57. Macro expansion(macroexpand (t guitar :specs :pickups :neck :model))
  58. 58. Macro expansion(macroexpand (t guitar :specs :pickups :neck :model));;expands to
  59. 59. Macro expansion(macroexpand (t guitar :specs :pickups :neck :model));;expands to(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))
  60. 60. 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])
  61. 61. 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))
  62. 62. 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
  63. 63. 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))))
  64. 64. 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)
  65. 65. Implementing unless We want... (unless (zero? 2) (print "Not zero!"))
  66. 66. 1st try - function
  67. 67. 1st try - function (defn unless [predicate body] (when (not predicate) body))
  68. 68. 1st try - function (defn unless [predicate body] (when (not predicate) body)) (unless (zero? 2) (print "Not zero!")) ;;Not zero!
  69. 69. 1st try - function (defn unless [predicate body] (when (not predicate) body)) (unless (zero? 2) (print "Not zero!")) ;;Not zero! (unless (zero? 0) (print "Not zero!")) ;;Not zero!
  70. 70. 1st try - function (defn unless [predicate body] (when (not predicate) body)) (unless (zero? 2) (print "Not zero!")) ;;Not zero! (unless (zero? 0) (print "Not zero!")) ;;Not zero! Oh noes!
  71. 71. 1st try - function Function arguments are eagerly evaluated!
  72. 72. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body))
  73. 73. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body)) (macroexpand (unless (zero? 2)
  74. 74. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body)) (macroexpand (unless (zero? 2) (print "Not zero!")))
  75. 75. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body)) (macroexpand (unless (zero? 2) (print "Not zero!"))) ;;expands to
  76. 76. 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))
  77. 77. 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!")))
  78. 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)
  79. 79. Thanks!Questions?! Leonardo Borges @leonardo_borges http://www.leonardoborges.com http://www.thoughtworks.com
  80. 80. 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_borges http://www.leonardoborges.com http://www.thoughtworks.com

×