Continuation-passing style and
     Macros with Clojure




                          Leonardo Borges
                          @leonardo_borges
                          http://www.leonardoborges.com
                          http://www.thoughtworks.com
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)
CPS - Pythagorean theorem
 You know the drill...
 a² + b² = c²

 ;;direct style
 (defn pyth [a b]
   (+ (* a a) (* b b)))
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))))))
WTF?!
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))))))
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
CPS - Fibonacci
;;direct style
(defn fib [n]
  (if (<= n 1)
    n
    (+ (fib (- n 1)) (fib (- n 2)))))
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
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
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
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
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
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)))
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
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
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
Seaside - Task example [1]

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




                                           [1] Try it yourself (http://bit.ly/seaside-task)
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 , '.'
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)
Macros

If you give someone Fortran, he has Fortran.
If you give someone Lisp, he has any language he pleases.
                                                            - Guy Steele
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
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"}})
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))))
e.g.: avoiding nesting levels

 what if we could achieve the same like this instead?

 (t guitar :specs :pickups :neck :model)
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)))
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)
What’s with all that `~@ ?
Quoting
Prevents evaluation
Quoting
Prevents evaluation

(def my-list (1 2 3))
Quoting
Prevents evaluation

(def my-list (1 2 3))
;java.lang.Integer cannot be cast to clojure.lang.IFn
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!
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
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)
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
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
Unquote

Evaluates some forms in a quoted expression
Unquote

Evaluates some forms in a quoted expression

Before unquoting...
Unquote

Evaluates some forms in a quoted expression

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

Evaluates some forms in a quoted expression

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

Evaluates some forms in a quoted expression

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

After...
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)
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)))
Unquote-splicing
Unpacks the sequence at hand
Unquote-splicing
Unpacks the sequence at hand

Before unquote-splicing...
Unquote-splicing
Unpacks the sequence at hand

Before unquote-splicing...
`(+ ~my-list)
Unquote-splicing
Unpacks the sequence at hand

Before unquote-splicing...
`(+ ~my-list)
;;(clojure.core/+ (1 2 3))
Unquote-splicing
Unpacks the sequence at hand

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


(eval `(+ ~my-list))
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
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...
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)
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)
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
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)))
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?
Macro expansion
Macro expansion
(macroexpand '(t guitar :specs :pickups :neck :model))
Macro expansion
(macroexpand '(t guitar :specs :pickups :neck :model))

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

;;expands to
(:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))
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])
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))
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
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))))
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)
Implementing unless

    We want...

    (unless (zero? 2)
      (print "Not zero!"))
1st try - function
1st try - function
 (defn unless [predicate body]
   (when (not predicate)
     body))
1st try - function
 (defn unless [predicate body]
   (when (not predicate)
     body))

 (unless (zero? 2)
   (print "Not zero!"))
 ;;Not zero!
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!
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!
1st try - function

 Function arguments are eagerly evaluated!
Unless - our second macro
    (defmacro unless [predicate body]
      `(when (not ~predicate)
         ~@body))
Unless - our second macro
    (defmacro unless [predicate body]
      `(when (not ~predicate)
         ~@body))

    (macroexpand '(unless (zero? 2)
Unless - our second macro
    (defmacro unless [predicate body]
      `(when (not ~predicate)
         ~@body))

    (macroexpand '(unless (zero? 2)
                    (print "Not zero!")))
Unless - our second macro
    (defmacro unless [predicate body]
      `(when (not ~predicate)
         ~@body))

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


    ;;expands to
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))
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!")))
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)
Thanks!

Questions?!
         Leonardo Borges
        @leonardo_borges
 http://www.leonardoborges.com
  http://www.thoughtworks.com
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

More Related Content

PDF
Clouds against the Floods (RubyConfBR2011)
PDF
Clojure Reducers / clj-syd Aug 2012
PDF
Pune Clojure Course Outline
ODP
Clojure made simple - Lightning talk
PDF
Clojure values
PDF
Clojure class
PPTX
Introduction to Clojure and why it's hot for Sart-Ups
Clouds against the Floods (RubyConfBR2011)
Clojure Reducers / clj-syd Aug 2012
Pune Clojure Course Outline
Clojure made simple - Lightning talk
Clojure values
Clojure class
Introduction to Clojure and why it's hot for Sart-Ups

Similar to Continuation Passing Style and Macros in Clojure - Jan 2012 (20)

PPT
PDF
Clojure - A new Lisp
PDF
Clojure A Dynamic Programming Language for the JVM
PDF
7li7w devcon5
PDF
The Magnificent Seven
KEY
Clojure Intro
ODP
Clojure: Practical functional approach on JVM
PPTX
Clojure 7-Languages
PDF
(first '(Clojure.))
PDF
Stefan Kanev: Clojure, ClojureScript and Why They're Awesome at I T.A.K.E. Un...
PDF
Clojure Interoperability
ODP
Getting started with Clojure
PDF
Clojure 1.1 And Beyond
PDF
Clojure intro
PDF
Clojure for Rubyists
PDF
Clojure - An Introduction for Lisp Programmers
PDF
Get into Functional Programming with Clojure
PDF
PDF
ClojureScript loves React, DomCode May 26 2015
ODP
Clojure made really really simple
Clojure - A new Lisp
Clojure A Dynamic Programming Language for the JVM
7li7w devcon5
The Magnificent Seven
Clojure Intro
Clojure: Practical functional approach on JVM
Clojure 7-Languages
(first '(Clojure.))
Stefan Kanev: Clojure, ClojureScript and Why They're Awesome at I T.A.K.E. Un...
Clojure Interoperability
Getting started with Clojure
Clojure 1.1 And Beyond
Clojure intro
Clojure for Rubyists
Clojure - An Introduction for Lisp Programmers
Get into Functional Programming with Clojure
ClojureScript loves React, DomCode May 26 2015
Clojure made really really simple
Ad

More from Leonardo Borges (19)

PDF
Realtime collaboration with Clojure - EuroClojure - Barcelona, 2015
PDF
Parametricity - #cljsyd - May, 2015
PDF
From Java to Parellel Clojure - Clojure South 2019
PDF
The algebra of library design
PDF
Futures e abstração - QCon São Paulo 2015
PDF
Functional Reactive Programming / Compositional Event Systems
PDF
High Performance web apps in Om, React and ClojureScript
PDF
Programação functional reativa: lidando com código assíncrono
PDF
Monads in Clojure
PDF
Clojure Macros Workshop: LambdaJam 2013 / CUFP 2013
PDF
Intro to Clojure's core.async
PDF
Functional Reactive Programming in Clojurescript
PDF
Clojure/West 2013 in 30 mins
PDF
The many facets of code reuse in JavaScript
PDF
Heroku addons development - Nov 2011
KEY
Clouds Against the Floods
KEY
Arel in Rails 3
PDF
Testing with Spring
PDF
JRuby in The Enterprise
Realtime collaboration with Clojure - EuroClojure - Barcelona, 2015
Parametricity - #cljsyd - May, 2015
From Java to Parellel Clojure - Clojure South 2019
The algebra of library design
Futures e abstração - QCon São Paulo 2015
Functional Reactive Programming / Compositional Event Systems
High Performance web apps in Om, React and ClojureScript
Programação functional reativa: lidando com código assíncrono
Monads in Clojure
Clojure Macros Workshop: LambdaJam 2013 / CUFP 2013
Intro to Clojure's core.async
Functional Reactive Programming in Clojurescript
Clojure/West 2013 in 30 mins
The many facets of code reuse in JavaScript
Heroku addons development - Nov 2011
Clouds Against the Floods
Arel in Rails 3
Testing with Spring
JRuby in The Enterprise
Ad

Recently uploaded (20)

PDF
Fitaura: AI & Machine Learning Powered Fitness Tracker
PDF
Secure Java Applications against Quantum Threats
PDF
ELLIE29.pdfWETWETAWTAWETAETAETERTRTERTER
PDF
EGCB_Solar_Project_Presentation_and Finalcial Analysis.pdf
PPTX
maintenance powerrpoint for adaprive and preventive
PDF
Addressing the challenges of harmonizing law and artificial intelligence tech...
PDF
The Digital Engine Room: Unlocking APAC’s Economic and Digital Potential thro...
PPTX
Presentation - Principles of Instructional Design.pptx
PPTX
From Curiosity to ROI — Cost-Benefit Analysis of Agentic Automation [3/6]
PPTX
Information-Technology-in-Human-Society.pptx
PDF
Ebook - The Future of AI A Comprehensive Guide.pdf
PDF
【AI論文解説】高速・高品質な生成を実現するFlow Map Models(Part 1~3)
PDF
Examining Bias in AI Generated News Content.pdf
PDF
1_Keynote_Breaking Barriers_한계를 넘어서_Charith Mendis.pdf
PPTX
CRM(Customer Relationship Managmnet) Presentation
PDF
Advancements in abstractive text summarization: a deep learning approach
PPT
Overviiew on Intellectual property right
PDF
NewMind AI Journal Monthly Chronicles - August 2025
PDF
CCUS-as-the-Missing-Link-to-Net-Zero_AksCurious.pdf
PPTX
Slides World Game (s) Great Redesign Eco Economic Epochs.pptx
Fitaura: AI & Machine Learning Powered Fitness Tracker
Secure Java Applications against Quantum Threats
ELLIE29.pdfWETWETAWTAWETAETAETERTRTERTER
EGCB_Solar_Project_Presentation_and Finalcial Analysis.pdf
maintenance powerrpoint for adaprive and preventive
Addressing the challenges of harmonizing law and artificial intelligence tech...
The Digital Engine Room: Unlocking APAC’s Economic and Digital Potential thro...
Presentation - Principles of Instructional Design.pptx
From Curiosity to ROI — Cost-Benefit Analysis of Agentic Automation [3/6]
Information-Technology-in-Human-Society.pptx
Ebook - The Future of AI A Comprehensive Guide.pdf
【AI論文解説】高速・高品質な生成を実現するFlow Map Models(Part 1~3)
Examining Bias in AI Generated News Content.pdf
1_Keynote_Breaking Barriers_한계를 넘어서_Charith Mendis.pdf
CRM(Customer Relationship Managmnet) Presentation
Advancements in abstractive text summarization: a deep learning approach
Overviiew on Intellectual property right
NewMind AI Journal Monthly Chronicles - August 2025
CCUS-as-the-Missing-Link-to-Net-Zero_AksCurious.pdf
Slides World Game (s) Great Redesign Eco Economic Epochs.pptx

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
  • 8. CPS - Fibonacci ;;direct style (defn fib [n] (if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))
  • 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. 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
  • 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. 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. 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. 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)
  • 19. 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 , '.'
  • 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
  • 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. 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. e.g.: avoiding nesting levels what if we could achieve the same like this instead? (t guitar :specs :pickups :neck :model)
  • 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. 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. What’s with all that `~@ ?
  • 31. Quoting Prevents evaluation (def my-list (1 2 3)) ;java.lang.Integer cannot be cast to clojure.lang.IFn
  • 32. 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!
  • 33. 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
  • 34. 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)
  • 35. 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
  • 36. 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
  • 37. Unquote Evaluates some forms in a quoted expression
  • 38. Unquote Evaluates some forms in a quoted expression Before unquoting...
  • 39. Unquote Evaluates some forms in a quoted expression Before unquoting... `(map even? my-list)
  • 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)))
  • 45. Unquote-splicing Unpacks the sequence at hand Before unquote-splicing...
  • 46. Unquote-splicing Unpacks the sequence at hand Before unquote-splicing... `(+ ~my-list)
  • 47. Unquote-splicing Unpacks the sequence at hand Before unquote-splicing... `(+ ~my-list) ;;(clojure.core/+ (1 2 3))
  • 48. Unquote-splicing Unpacks the sequence at hand Before unquote-splicing... `(+ ~my-list) ;;(clojure.core/+ (1 2 3)) (eval `(+ ~my-list))
  • 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?
  • 57. Macro expansion (macroexpand '(t guitar :specs :pickups :neck :model))
  • 58. Macro expansion (macroexpand '(t guitar :specs :pickups :neck :model)) ;;expands to
  • 59. Macro expansion (macroexpand '(t guitar :specs :pickups :neck :model)) ;;expands to (:model (user/t (user/t (user/t guitar :specs) :pickups) :neck))
  • 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. 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. 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. 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. 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. Implementing unless We want... (unless (zero? 2) (print "Not zero!"))
  • 66. 1st try - function
  • 67. 1st try - function (defn unless [predicate body] (when (not predicate) body))
  • 68. 1st try - function (defn unless [predicate body] (when (not predicate) body)) (unless (zero? 2) (print "Not zero!")) ;;Not zero!
  • 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. 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. 1st try - function Function arguments are eagerly evaluated!
  • 72. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body))
  • 73. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body)) (macroexpand '(unless (zero? 2)
  • 74. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body)) (macroexpand '(unless (zero? 2) (print "Not zero!")))
  • 75. Unless - our second macro (defmacro unless [predicate body] `(when (not ~predicate) ~@body)) (macroexpand '(unless (zero? 2) (print "Not zero!"))) ;;expands to
  • 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. 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. 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. Thanks! Questions?! Leonardo Borges @leonardo_borges http://www.leonardoborges.com http://www.thoughtworks.com
  • 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