SlideShare a Scribd company logo
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

ZIP
Lisp Macros in 20 Minutes (Featuring Clojure)
PDF
Functional Programming with Groovy
PDF
Practical Groovy DSL
PDF
F# for C# Programmers
PDF
Prml4.4 ラプラス近似~ベイズロジスティック回帰
PDF
The Power of Composition
PDF
The Power of Composition (NDC Oslo 2020)
PPTX
Mysql creating stored function
Lisp Macros in 20 Minutes (Featuring Clojure)
Functional Programming with Groovy
Practical Groovy DSL
F# for C# Programmers
Prml4.4 ラプラス近似~ベイズロジスティック回帰
The Power of Composition
The Power of Composition (NDC Oslo 2020)
Mysql creating stored function

What's hot (20)

PDF
Deep Mixtures of Factor Analysers
PPTX
Boost your productivity with Scala tooling!
PDF
Tdd with python unittest for embedded c
PDF
The Future of Java: Records, Sealed Classes and Pattern Matching
PDF
Sql
PDF
The Functional Programming Toolkit (NDC Oslo 2019)
PPTX
Euler's Method
PDF
Python functions
PDF
パターン認識と機械学習 §6.2 カーネル関数の構成
PPTX
全体セミナーWfst
PDF
Spray Json and MongoDB Queries: Insights and Simple Tricks.
PDF
ディジタル信号処理の課題解説 その2
PDF
The lazy programmer's guide to writing thousands of tests
PDF
Pipeline oriented programming
PPTX
Arrays.pptx
PDF
PRML 2.3.2-2.3.4 ガウス分布
PDF
正規表現を覚えよう(中級編)
PDF
Modern Programming in Java 8 - Lambdas, Streams and Date Time API
PDF
Practical Object Oriented Models In Sql
PDF
Exercices pascal tous les chapitres
Deep Mixtures of Factor Analysers
Boost your productivity with Scala tooling!
Tdd with python unittest for embedded c
The Future of Java: Records, Sealed Classes and Pattern Matching
Sql
The Functional Programming Toolkit (NDC Oslo 2019)
Euler's Method
Python functions
パターン認識と機械学習 §6.2 カーネル関数の構成
全体セミナーWfst
Spray Json and MongoDB Queries: Insights and Simple Tricks.
ディジタル信号処理の課題解説 その2
The lazy programmer's guide to writing thousands of tests
Pipeline oriented programming
Arrays.pptx
PRML 2.3.2-2.3.4 ガウス分布
正規表現を覚えよう(中級編)
Modern Programming in Java 8 - Lambdas, Streams and Date Time API
Practical Object Oriented Models In Sql
Exercices pascal tous les chapitres
Ad

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

PDF
Introduction To Lisp
PDF
ClojureScript loves React, DomCode May 26 2015
PDF
[FT-11][suhorng] “Poor Man's” Undergraduate Compilers
PDF
Fibonacci Function Gallery - Part 2 - One in a series
KEY
(map Clojure everyday-tasks)
PDF
Pune Clojure Course Outline
KEY
Clojure Intro
ODP
Clojure: Practical functional approach on JVM
PDF
Functional Reactive Programming / Compositional Event Systems
PDF
Writing Macros
PDF
JavaScript 2016 for C# Developers
PDF
Clojure 1.1 And Beyond
PPTX
Angular2 for Beginners
ODP
RailswayCon 2010 - Dynamic Language VMs
PDF
Clojure+ClojureScript Webapps
PDF
R and cpp
PDF
Full Stack Clojure
PDF
Optimizing with persistent data structures (LLVM Cauldron 2016)
PDF
Faster Python, FOSDEM
PDF
Compiler Construction | Lecture 13 | Code Generation
Introduction To Lisp
ClojureScript loves React, DomCode May 26 2015
[FT-11][suhorng] “Poor Man's” Undergraduate Compilers
Fibonacci Function Gallery - Part 2 - One in a series
(map Clojure everyday-tasks)
Pune Clojure Course Outline
Clojure Intro
Clojure: Practical functional approach on JVM
Functional Reactive Programming / Compositional Event Systems
Writing Macros
JavaScript 2016 for C# Developers
Clojure 1.1 And Beyond
Angular2 for Beginners
RailswayCon 2010 - Dynamic Language VMs
Clojure+ClojureScript Webapps
R and cpp
Full Stack Clojure
Optimizing with persistent data structures (LLVM Cauldron 2016)
Faster Python, FOSDEM
Compiler Construction | Lecture 13 | Code Generation
Ad

More from Leonardo Borges (20)

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
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
Clojure Reducers / clj-syd Aug 2012
PDF
The many facets of code reuse in JavaScript
PDF
Heroku addons development - Nov 2011
PDF
Clouds against the Floods (RubyConfBR2011)
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
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
Clojure Reducers / clj-syd Aug 2012
The many facets of code reuse in JavaScript
Heroku addons development - Nov 2011
Clouds against the Floods (RubyConfBR2011)
Clouds Against the Floods
Arel in Rails 3
Testing with Spring
JRuby in The Enterprise

Recently uploaded (20)

PDF
Assigned Numbers - 2025 - Bluetooth® Document
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
A Presentation on Artificial Intelligence
PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
A comparative analysis of optical character recognition models for extracting...
PDF
Electronic commerce courselecture one. Pdf
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPTX
Machine Learning_overview_presentation.pptx
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
Big Data Technologies - Introduction.pptx
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Empathic Computing: Creating Shared Understanding
PPTX
Tartificialntelligence_presentation.pptx
PDF
NewMind AI Weekly Chronicles - August'25-Week II
Assigned Numbers - 2025 - Bluetooth® Document
Programs and apps: productivity, graphics, security and other tools
A Presentation on Artificial Intelligence
Accuracy of neural networks in brain wave diagnosis of schizophrenia
Digital-Transformation-Roadmap-for-Companies.pptx
20250228 LYD VKU AI Blended-Learning.pptx
A comparative analysis of optical character recognition models for extracting...
Electronic commerce courselecture one. Pdf
MIND Revenue Release Quarter 2 2025 Press Release
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Machine Learning_overview_presentation.pptx
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
Spectral efficient network and resource selection model in 5G networks
Big Data Technologies - Introduction.pptx
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Encapsulation_ Review paper, used for researhc scholars
Network Security Unit 5.pdf for BCA BBA.
Empathic Computing: Creating Shared Understanding
Tartificialntelligence_presentation.pptx
NewMind AI Weekly Chronicles - August'25-Week II

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