Macros in Clojure
      @athos0220
Agenda

• Lispマクロ入門
• Clojureのマクロとその周辺
• Clojureマクロの応用例
 Lispマクロ入門
マクロとは
                when                                       if



      =        print    print          =                   do            nil



mod       0    "Fizz"   "Buzz"   mod         0    print         print



 x        15                      x         15    "Fizz"        "Buzz"


                                           (if (= (mod x 15) 0)
(when (= (mod x 15) 0)                       (do
  (print “Fizz”)                               (print “Fizz”)
  (print “Buzz”))                              (print “Buzz”))
                                             nil)

             構文木を組み替えるための仕組み
           →自由に構文を作ることができる仕組み
マクロの定義
• シンボルやリストで展開形のコードを
 作ってやる
  (when ⃝⃝   (if ⃝⃝
    △△         (do △△
    ✕✕)             ✕✕)
               nil)
マクロの定義
    • シンボルやリストで展開形のコードを
       作ってやる
           (when ⃝⃝            (if ⃝⃝
             △△                  (do △△
             ✕✕)                      ✕✕)
                                 nil)
(defmacro when [test & body]
  (list ’if
        test
        (cons ’do body)
        nil))
マクロの定義
    • シンボルやリストで展開形のコードを
       作ってやる
           (when ⃝⃝              (if ⃝⃝
             △△                    (do △△
             ✕✕)                        ✕✕)
                                   nil)
(defmacro when [test & body]   (defmacro when [test & body]
  (list ’if                      `(if ~test
        test                        (do ~@body)
        (cons ’do body)             nil)
        nil))
名前衝突の問題
名前衝突の問題
束縛変数の衝突   自由変数の衝突
名前衝突の問題
束縛変数の衝突                  自由変数の衝突
     (defmacro or [expr1 expr2]
       `(let [val ~expr1]
          (if val
            val
            ~expr2)))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]
  (or false val))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]
  (or false val))


(let [val true]
  (let [val false]
    (if val
      val
      val))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]
  (or false val))


(let [val true]
  (let [val false]
    (if val
      val
      val))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]
  (or false val))


(let [val true]
  (let [val false]
    (if val
      val
      val))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]                  (let [let nil]
  (or false val))                  (or false true))


(let [val true]
  (let [val false]
    (if val
      val
      val))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]                  (let [let nil]
  (or false val))                  (or false true))


(let [val true]                 (let [let nil]
  (let [val false]                (let [val false]
    (if val                         (if val
      val                             val
      val))                           true))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]                  (let [let nil]
  (or false val))                  (or false true))


(let [val true]                 (let [let nil]
  (let [val false]                (let [val false]
    (if val                         (if val
      val                             val
      val))                           true))
名前衝突の問題
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val ~expr1]
                   (if val
                     val
                     ~expr2)))
(let [val true]                  (let [let nil]
  (or false val))                  (or false true))


(let [val true]                 (let [let nil]
  (let [val false]                (let [val false]
    (if val                         (if val
      val                             val
      val))                           true))
健全なマクロ (Hygienic macros)

• 名前の衝突を自動的に回避してくれる
  ハイパーなマクロ
健全なマクロ (Hygienic macros)

   • 名前の衝突を自動的に回避してくれる
     ハイパーなマクロ
(define-syntax or
  (syntax-rules ()
   ((or expr1 expr2)
    (let ([val expr1])
      (if val
        val
        expr2)))))
健全なマクロ (Hygienic macros)

   • 名前の衝突を自動的に回避してくれる
     ハイパーなマクロ
(define-syntax or        (let ([val true])
  (syntax-rules ()         (or false val))
   ((or expr1 expr2)
    (let ([val expr1])
      (if val
        val
        expr2)))))
健全なマクロ (Hygienic macros)

   • 名前の衝突を自動的に回避してくれる
     ハイパーなマクロ
(define-syntax or        (let ([val true])
  (syntax-rules ()         (or false val))
   ((or expr1 expr2)
    (let ([val expr1])
      (if val            (let ([val true])
        val                (let ([val_0 false])
        expr2)))))           (if val_0
                               val_0
                               val)))
マクロの分類
• 高レベル:専用のパターン言語をもつ
• 低レベル:Lispの関数でコードを操作する
          低レベル                 高レベル
不
健 • 伝統的なマクロ
全

健 • syntactic closures
  • explicit renaming    • syntax-rules
全 • syntax-case
マクロの分類
• 高レベル:専用のパターン言語をもつ
• 低レベル:Lispの関数でコードを操作する
          低レベル                 高レベル
不
健 • 伝統的なマクロ
全
                     >
健 • syntactic closures
  • explicit renaming    • syntax-rules
全 • syntax-case
マクロの分類
• 高レベル:専用のパターン言語をもつ
• 低レベル:Lispの関数でコードを操作する
          低レベル                 高レベル
不
健 • 伝統的なマクロ
全
            >
                     >
健 • syntactic closures
  • explicit renaming    • syntax-rules
全 • syntax-case
 Clojureの
マクロシステムとその周辺
Clojureのマクロシステム


• 伝統的なマクロがベース
                              低レベル                   高レベル


                     不
                     健   • 伝統的なマクロ
                     全




                               >
• syntax-quote が特徴


                                          >
                     健   • syntactic closures
                         • explicit renaming    • syntax-rules
                     全   • syntax-case
syntax-quote
syntax-quote
束縛変数の衝突    自由変数の衝突
syntax-quote
束縛変数の衝突                  自由変数の衝突
     (defmacro or [expr1 expr2]
       `(let [val# ~expr1]
          (if val#
            val#
            ~expr2)))
syntax-quote
束縛変数の衝突                         自由変数の衝突
            (defmacro or [expr1 expr2]
              `(let [val# ~expr1]
                 (if val#
 syntax-quote      val#
                   ~expr2)))
syntax-quote
束縛変数の衝突                           自由変数の衝突
              (defmacro or [expr1 expr2]
                `(let [val# ~expr1]
                   (if val#
   syntax-quote      val#
                     ~expr2)))
(let [val true]
  (or false val))
syntax-quote
        束縛変数の衝突                                自由変数の衝突
                        (defmacro or [expr1 expr2]
                          `(let [val# ~expr1]
                             (if val#
             syntax-quote      val#
                               ~expr2)))
          (let [val true]
            (or false val))



(let [val true]
  (clojure.core/let [val__419__auto__ false]
    (if val__419__auto__
      val__419__auto__
      val))

#をつけた名前を自動でリネーム (auto-gensym)
syntax-quote
        束縛変数の衝突                                自由変数の衝突
                        (defmacro or [expr1 expr2]
                          `(let [val# ~expr1]
                             (if val#
             syntax-quote      val#
                               ~expr2)))
          (let [val true]                     (let [let nil]
            (or false val))                     (or false true))



(let [val true]
  (clojure.core/let [val__419__auto__ false]
    (if val__419__auto__
      val__419__auto__
      val))

#をつけた名前を自動でリネーム (auto-gensym)
syntax-quote
        束縛変数の衝突                                         自由変数の衝突
                        (defmacro or [expr1 expr2]
                          `(let [val# ~expr1]
                             (if val#
             syntax-quote      val#
                               ~expr2)))
          (let [val true]                     (let [let nil]
            (or false val))                     (or false true))



(let [val true]                                (let [let nil]
  (clojure.core/let [val__419__auto__ false]     (clojure.core/let [val__419__auto__ false]
    (if val__419__auto__                           (if val__419__auto__
      val__419__auto__                               val__419__auto__
      val))                                          true))

#をつけた名前を自動でリネーム (auto-gensym) その他の名前にはnamespace名をqualifyする
メタデータ
    •   ほとんどすべてのオブジェクトにメタデータを付加できる

    •   Clojureではコードもデータ

        →コードのほとんどあらゆる部分にメタデータをアノテー
         ションとして付加できる
                                 名前をnamespaceにプライベートにする
               (defn ^:private f [x] x)

               (def ^:dynamic x nil)
動的スコープの変数にする                            戻り値と引数の型ヒント
               (defn fact ^long [^long x]
                 (if (= x 0)
                   1
                   (* x (fact (- x 1)))))
Java Interop

• ClojureからJavaのクラスにアクセスできる
• Clojureは大部分がJavaで書かれている
• Javaで書かれているClojureのコンパイラ自
 体や内部で使われる構文木にも触れる
暗黙の引数 &formと&env

• マクロ呼出しのフォームを囲むコンテ
 キストに関する情報が渡ってくる

 -   &form:マクロ呼出しのフォーム全体

 -   &env:マクロ呼出しの時点で見えて
     いるローカル環境
 Clojure
マクロの応用例
the オペレータ   http://d.hatena.ne.jp/athos/20120129/THE_operator_in_clojure




   •   「型を1つ引数にとり現在のスコープに唯一存
       在するその型のオブジェクトを返す演算子」
       by @kinaba
   •   &envを使って、そのスコープで見えている変
       数のうち、指定した型がメタデータに付いて
       いるものを拾ってくる
(let [^File _ (File. "foo.txt")
      ^FileReader __ (FileReader. (the File))
      ^BufferedReader ___ (BufferedReader (the FileReader))]
  (.readLine (the BufferedReader)))
inline assembler マクロ                         http://www.slideshare.net/sohta/shibuyalisp-tt7




         • Clojureコンパイラが使うバイトコード
              生成ライブラリをマクロ展開時に使う
(def fact                                            public final java.lang.Object invoke(java.lang.
  (fn-iasm [n]                                         Code:
    (aload_1)                                           Stack=2, Locals=6, Args_size=2
    (checkcast Integer)                                 0:   aload_1
    (invokevirtual ^int Integer/intValue [])            1:   checkcast      #25; //class java/lang/I
    (istore_2)                                          4:   invokevirtual #29; //Method java/lang/
    (iconst_1)                                          7:   istore_2
    (istore_3)                                          8:   iconst_1
    :loop                                               9:   istore_3
    (iload_2)                                           10: iload_2
    (ifeq :end)                                         11: ifeq      24
    (ilaod_2)                                           14: iload_2
    (iload_3)                                           15: iload_3
    (imul)                                              16: imul
    (istore_3)                                          17: istore_3
    (iinc 2 -1)                                         18: iinc      2, -1
    (goto :loop)                                        21: goto      10
    :end                                                24: iload_3
    (iload_3)                                           25: invokestatic    #33; //Method java/lang/
    (invokestatic ^Integer Integer/valueOf [int])       28: areturn
    (areturn)))
syntactic-closure
                        http://d.hatena.ne.jp/athos/20120506/syntactic_closure_in_clojure



•   syntactic closuresによる健全なマクロを定義で
    きるようにするライブラリ
                                                                  低レベル              高レベル


•   syntax-quoteで定義できない                                  不
                                                         健
                                                         全
                                                             • 伝統的なマクロ


    ある種のマクロが定義可能に
                                                             • syntactic closures
(define-syntax or [expr1 expr2]           健
                                             • explicit renaming • syntax-rules
                                          全  • syntax-case
  (sc-macro-transformer
    (fn [env]
      (quasiquote
        (let [val ~(make-syntactic-closure env nil expr1)]
          (if val
            val
            ~(make-syntactic-closure env nil expr2)))))))

(let [val true]                       (let [val true]
  (or false val))                       (let [val396 false]
                                          (if val396 val396 val))
 まとめ

• Clojureのマクロ周りには遊べるおも
 ちゃがたくさん

• アイデア次第で貢献できる可能性?
• nagoya-lispに参加しましょう
おわり

Macros in Clojure

  • 1.
  • 2.
  • 3.
  • 4.
    マクロとは when if = print print = do nil mod 0 "Fizz" "Buzz" mod 0 print print x 15 x 15 "Fizz" "Buzz" (if (= (mod x 15) 0) (when (= (mod x 15) 0) (do (print “Fizz”) (print “Fizz”) (print “Buzz”)) (print “Buzz”)) nil) 構文木を組み替えるための仕組み →自由に構文を作ることができる仕組み
  • 5.
    マクロの定義 • シンボルやリストで展開形のコードを 作ってやる (when ⃝⃝ (if ⃝⃝ △△ (do △△ ✕✕) ✕✕) nil)
  • 6.
    マクロの定義 • シンボルやリストで展開形のコードを 作ってやる (when ⃝⃝ (if ⃝⃝ △△ (do △△ ✕✕) ✕✕) nil) (defmacro when [test & body] (list ’if test (cons ’do body) nil))
  • 7.
    マクロの定義 • シンボルやリストで展開形のコードを 作ってやる (when ⃝⃝ (if ⃝⃝ △△ (do △△ ✕✕) ✕✕) nil) (defmacro when [test & body] (defmacro when [test & body] (list ’if `(if ~test test (do ~@body) (cons ’do body) nil) nil))
  • 8.
  • 9.
  • 10.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2)))
  • 11.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (or false val))
  • 12.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (or false val)) (let [val true] (let [val false] (if val val val))
  • 13.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (or false val)) (let [val true] (let [val false] (if val val val))
  • 14.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (or false val)) (let [val true] (let [val false] (if val val val))
  • 15.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (let [let nil] (or false val)) (or false true)) (let [val true] (let [val false] (if val val val))
  • 16.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (let [let nil] (or false val)) (or false true)) (let [val true] (let [let nil] (let [val false] (let [val false] (if val (if val val val val)) true))
  • 17.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (let [let nil] (or false val)) (or false true)) (let [val true] (let [let nil] (let [val false] (let [val false] (if val (if val val val val)) true))
  • 18.
    名前衝突の問題 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val ~expr1] (if val val ~expr2))) (let [val true] (let [let nil] (or false val)) (or false true)) (let [val true] (let [let nil] (let [val false] (let [val false] (if val (if val val val val)) true))
  • 19.
    健全なマクロ (Hygienic macros) •名前の衝突を自動的に回避してくれる ハイパーなマクロ
  • 20.
    健全なマクロ (Hygienic macros) • 名前の衝突を自動的に回避してくれる ハイパーなマクロ (define-syntax or (syntax-rules () ((or expr1 expr2) (let ([val expr1]) (if val val expr2)))))
  • 21.
    健全なマクロ (Hygienic macros) • 名前の衝突を自動的に回避してくれる ハイパーなマクロ (define-syntax or (let ([val true]) (syntax-rules () (or false val)) ((or expr1 expr2) (let ([val expr1]) (if val val expr2)))))
  • 22.
    健全なマクロ (Hygienic macros) • 名前の衝突を自動的に回避してくれる ハイパーなマクロ (define-syntax or (let ([val true]) (syntax-rules () (or false val)) ((or expr1 expr2) (let ([val expr1]) (if val (let ([val true]) val (let ([val_0 false]) expr2))))) (if val_0 val_0 val)))
  • 23.
    マクロの分類 • 高レベル:専用のパターン言語をもつ • 低レベル:Lispの関数でコードを操作する 低レベル 高レベル 不 健 • 伝統的なマクロ 全 健 • syntactic closures • explicit renaming • syntax-rules 全 • syntax-case
  • 24.
    マクロの分類 • 高レベル:専用のパターン言語をもつ • 低レベル:Lispの関数でコードを操作する 低レベル 高レベル 不 健 • 伝統的なマクロ 全 > 健 • syntactic closures • explicit renaming • syntax-rules 全 • syntax-case
  • 25.
    マクロの分類 • 高レベル:専用のパターン言語をもつ • 低レベル:Lispの関数でコードを操作する 低レベル 高レベル 不 健 • 伝統的なマクロ 全 > > 健 • syntactic closures • explicit renaming • syntax-rules 全 • syntax-case
  • 26.
  • 27.
    Clojureのマクロシステム • 伝統的なマクロがベース 低レベル 高レベル 不 健 • 伝統的なマクロ 全 > • syntax-quote が特徴 > 健 • syntactic closures • explicit renaming • syntax-rules 全 • syntax-case
  • 28.
  • 29.
  • 30.
    syntax-quote 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val# ~expr1] (if val# val# ~expr2)))
  • 31.
    syntax-quote 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val# ~expr1] (if val# syntax-quote val# ~expr2)))
  • 32.
    syntax-quote 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val# ~expr1] (if val# syntax-quote val# ~expr2))) (let [val true] (or false val))
  • 33.
    syntax-quote 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val# ~expr1] (if val# syntax-quote val# ~expr2))) (let [val true] (or false val)) (let [val true] (clojure.core/let [val__419__auto__ false] (if val__419__auto__ val__419__auto__ val)) #をつけた名前を自動でリネーム (auto-gensym)
  • 34.
    syntax-quote 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val# ~expr1] (if val# syntax-quote val# ~expr2))) (let [val true] (let [let nil] (or false val)) (or false true)) (let [val true] (clojure.core/let [val__419__auto__ false] (if val__419__auto__ val__419__auto__ val)) #をつけた名前を自動でリネーム (auto-gensym)
  • 35.
    syntax-quote 束縛変数の衝突 自由変数の衝突 (defmacro or [expr1 expr2] `(let [val# ~expr1] (if val# syntax-quote val# ~expr2))) (let [val true] (let [let nil] (or false val)) (or false true)) (let [val true] (let [let nil] (clojure.core/let [val__419__auto__ false] (clojure.core/let [val__419__auto__ false] (if val__419__auto__ (if val__419__auto__ val__419__auto__ val__419__auto__ val)) true)) #をつけた名前を自動でリネーム (auto-gensym) その他の名前にはnamespace名をqualifyする
  • 36.
    メタデータ • ほとんどすべてのオブジェクトにメタデータを付加できる • Clojureではコードもデータ →コードのほとんどあらゆる部分にメタデータをアノテー ションとして付加できる 名前をnamespaceにプライベートにする (defn ^:private f [x] x) (def ^:dynamic x nil) 動的スコープの変数にする 戻り値と引数の型ヒント (defn fact ^long [^long x] (if (= x 0) 1 (* x (fact (- x 1)))))
  • 37.
    Java Interop • ClojureからJavaのクラスにアクセスできる •Clojureは大部分がJavaで書かれている • Javaで書かれているClojureのコンパイラ自 体や内部で使われる構文木にも触れる
  • 38.
    暗黙の引数 &formと&env • マクロ呼出しのフォームを囲むコンテ キストに関する情報が渡ってくる - &form:マクロ呼出しのフォーム全体 - &env:マクロ呼出しの時点で見えて いるローカル環境
  • 39.
  • 40.
    the オペレータ http://d.hatena.ne.jp/athos/20120129/THE_operator_in_clojure • 「型を1つ引数にとり現在のスコープに唯一存 在するその型のオブジェクトを返す演算子」 by @kinaba • &envを使って、そのスコープで見えている変 数のうち、指定した型がメタデータに付いて いるものを拾ってくる (let [^File _ (File. "foo.txt") ^FileReader __ (FileReader. (the File)) ^BufferedReader ___ (BufferedReader (the FileReader))] (.readLine (the BufferedReader)))
  • 41.
    inline assembler マクロ http://www.slideshare.net/sohta/shibuyalisp-tt7 • Clojureコンパイラが使うバイトコード 生成ライブラリをマクロ展開時に使う (def fact public final java.lang.Object invoke(java.lang. (fn-iasm [n] Code: (aload_1) Stack=2, Locals=6, Args_size=2 (checkcast Integer) 0: aload_1 (invokevirtual ^int Integer/intValue []) 1: checkcast #25; //class java/lang/I (istore_2) 4: invokevirtual #29; //Method java/lang/ (iconst_1) 7: istore_2 (istore_3) 8: iconst_1 :loop 9: istore_3 (iload_2) 10: iload_2 (ifeq :end) 11: ifeq 24 (ilaod_2) 14: iload_2 (iload_3) 15: iload_3 (imul) 16: imul (istore_3) 17: istore_3 (iinc 2 -1) 18: iinc 2, -1 (goto :loop) 21: goto 10 :end 24: iload_3 (iload_3) 25: invokestatic #33; //Method java/lang/ (invokestatic ^Integer Integer/valueOf [int]) 28: areturn (areturn)))
  • 42.
    syntactic-closure http://d.hatena.ne.jp/athos/20120506/syntactic_closure_in_clojure • syntactic closuresによる健全なマクロを定義で きるようにするライブラリ 低レベル 高レベル • syntax-quoteで定義できない 不 健 全 • 伝統的なマクロ ある種のマクロが定義可能に • syntactic closures (define-syntax or [expr1 expr2] 健 • explicit renaming • syntax-rules 全 • syntax-case (sc-macro-transformer (fn [env] (quasiquote (let [val ~(make-syntactic-closure env nil expr1)] (if val val ~(make-syntactic-closure env nil expr2))))))) (let [val true] (let [val true] (or false val)) (let [val396 false] (if val396 val396 val))
  • 43.
     まとめ • Clojureのマクロ周りには遊べるおも ちゃがたくさん •アイデア次第で貢献できる可能性? • nagoya-lispに参加しましょう
  • 44.