--- title: 16.1 tags: The Racket Guide slideOptions: theme: white slideNumber: 'c/t' center: false transition: 'none' keyboard: true width: '93%' height: '100%' --- <style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.8; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #444; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5 {font-weight: normal;}, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 3.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 1.8vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h1 { margin: 0% -100%; padding: 2% 100% 4% 100%; color: #fff; background: #fffa5e; /* fallback for old browsers */ background: linear-gradient(-45deg, #f7f439, #54ffa4, #23A6D5, #238ed5); background-size: 200% 200%; animation: Gradient 60s ease infinite; } @keyframes Gradient { 0% {background-position: 0% 50%} 50% {background-position: 100% 50%} 100% {background-position: 0% 50%} } .reveal h2 { text-align: center; margin: -5% -50% 2% -50%; padding: 3% 10% 1% 10%; color: #fff; background: #fffa5e; /* fallback for old browsers */ background: -webkit-linear-gradient(to right, #c74646, #fffa5e); /* Chrome 10-25, Safari 5.1-6 */ background: linear-gradient(to right, #c74646, #fffa5e); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ } .reveal h4 { text-align: center; } </style> --- ## 16.1 パターンベースのマクロ パターンベースのマクロは、パターンにマッチするコードを、パターンの一部にマッチする元の構文の部分を使用する展開に置き換えます。 --- ## 16.1.1 define-syntax-rule マクロを作成する最も簡単な方法は、define-syntax-rule を使用することです。 ```ocaml (define-syntax-rule pattern template) ``` 実行例として、2 つの変数に格納されている値を交換する swap マクロを考えてみましょう。このマクロは、define-syntax-rule を使用して次のように実装できます。 <div class="box">このマクロは、変数への副作用を伴うという意味では「ラッキーではない」ものですが、マクロのポイントは、他の言語設計者が認めないような構文形式を追加できることです。 </div> ```ocaml (define-syntax-rule (swap x y) (let ([tmp x]) (set! x y) (set! y tmp))) ``` define-syntax-rule形式は、1つのパターンにマッチするマクロを結合します。パターンは常に開括弧で始まり、その後に識別子が続く必要があります。最初の識別子の後の他の識別子は、マクロのパターン変数で、マクロの使用中に何にでもマッチします。したがって、このマクロは、任意の form1 および form2 に対して (swap form1 form2) という形でマッチします。 <div class="box"> マクロ パターン変数は、match のパターン変数に似ています。関連項目: パターン マッチング。 </div> define-syntax-rule のパターンの後には、テンプレートがあります。テンプレートは、パターンにマッチするフォームの代わりに使用されます。ただし、テンプレート内のパターン変数の各インスタンスは、パターン変数がマッチしたマクロの使用部分で置き換えられます。たとえば、次のようになります。 ```ocaml (swap first last) ``` では,パターン変数 x が最初に,y が最後にマッチするので,次のような展開になります。 ```ocaml (let ([tmp first]) (set! first last) (set! last tmp)) ``` --- ## 16.1.2 レキシカルスコープ swap マクロを使って,tmp と other という名前の変数を入れ替えるとします。 ```ocaml (let ([tmp 5] [other 6]) (swap tmp other) (list tmp other)) ``` 上の式の結果は(6 5)になるはずです。しかし,この swap の使い方を素朴に展開すると,次のようになります ```ocaml (let ([tmp 5] [other 6]) (let ([tmp tmp]) (set! tmp other) (set! other tmp)) (list tmp other)) ``` となり,結果は(5 6)となります.問題は、swapが使われている文脈でのtmpと、マクロのテンプレートにあるtmpが混同されていることです。 Racketは、上記のswapの使用に対して、素朴な展開を行いません。その代わりに、次のように生成します。 ```ocaml (let ([tmp 5] [other 6]) (let ([tmp_1 tmp]) (set! tmp other) (set! other tmp_1)) (list tmp other)) ``` Racketは、上記のswapの使用に対して、素朴な展開を行いません。その代わりに、次のように生成します。 ```ocaml (let ([set! 5] [other 6]) (swap set! other) (list set! other)) ``` 展開は次のとおりです。 ```ocaml (let ([set!_1 5] [other 6]) (let ([tmp set!_1]) (set! set!_1 other) (set! other tmp)) (list set!_1 other)) ``` これは、ローカルのset!バインディングが、マクロテンプレートによって導入された割り当てを妨害しないようにするためです。 つまり、Racket のパターンベースのマクロは自動的にレキシカル スコープを維持するので、マクロの実装者は、関数や関数呼び出しと同じように、マクロ内の変数参照やマクロの使用について推論できます。 --- ## 16.1.3 define-syntax 及び syntax-rule define-syntax-rule 形式は、単一のパターンに一致するマクロを結合しますが、Racket のマクロシステムは、同じ識別子で始まる複数のパターンに一致する変換器をサポートします。このようなマクロを記述するには、プログラマーは、より一般的な define-yntax 形式と syntax-rules トランスフォーマ形式を使用する必要があります。 <div class="box"> define-syntax-rule形式は、それ自体がdefine-syntaxに展開されるマクロであり、1つのパターンとテンプレートのみを含むsyntax-rules形式を備えています。 </div> <br> たとえば、swap を一般化して 2 つまたは 3 つの識別子で動作する rotate マクロが必要だとします。 ```ocaml (let ([red 1] [green 2] [blue 3]) (rotate red green) ; swaps (rotate red green blue) ; rotates left (list red green blue)) ``` は (1 3 2) を生成します。この場合、syntax-rulesを使用してrotateを実装できます。 ```ocaml (define-syntax rotate (syntax-rules () [(rotate a b) (swap a b)] [(rotate a b c) (begin (swap a b) (swap b c))])) ``` 式 (rotate red green) は syntax-rules 形式の最初のパターンに一致するため、(swap red green) に展開されます。式 (rotate red green blue) は 2 番目のパターンに一致するので、(begin (swap red green) (swap green blue)) に展開されます。 --- ## 16.1.4 一連のマッチング rotate マクロは、2 つまたは 3 つの識別子だけでなく、任意の数の識別子を使用できます。rotate の使用と任意の数の識別子を一致させるには、クレーネ星のようなパターンフォームが必要です。Racket のマクロパターンでは、星は .... のように記述されます。 rotate with ... を実装するには、単一の識別子を処理する基本ケースと、複数の識別子を処理する帰納的ケースが必要です。 ```ocaml (define-syntax rotate (syntax-rules () [(rotate a) (void)] [(rotate a b c ...) (begin (swap a b) (rotate b c ...))])) ``` c のようなパターン変数がパターンの中で ... の後に続くとき,テンプレートの中でも ... の後に続く必要があります.パターン変数は実質的に0個以上のフォームのシーケンスにマッチし、テンプレートでは同じシーケンスで置き換えられます。 これまでのrotateの両バージョンは、ペアワイズスワッピングが最後の変数に到達するまで、最初の変数からシーケンス内のすべての変数に値を移動し続けるので、少し効率が悪いです。より効率的なrotateでは、最初の値を最後の変数に直接移動させます。...パターンを使用して、ヘルパーマクロを使ってより効率的なバリアントを実装することができます。 ```ocaml (define-syntax rotate (syntax-rules () [(rotate a c ...) (shift-to (c ... a) (a c ...))])) (define-syntax shift-to (syntax-rules () [(shift-to (from0 from ...) (to0 to ...)) (let ([tmp from0]) (set! to from) ... (set! to0 tmp))])) ``` shift-to マクロでは,テンプレート内の ... が (set! to from) の後に続き,(set! to from) 式が,to および from シーケンスでマッチした各識別子を使用するのに必要な回数だけ複製されます。(toとfromのマッチ数は同じでなければならず、そうでなければマクロ展開はエラーで失敗します)。 --- ## 16.1.5 識別子マクロ マクロの定義では、swap または rotate 識別子を開括弧の後に使用する必要があり、そうでない場合は構文エラーが報告されます。 ```ocaml > (+ swap 3) eval:2:0: swap: bad syntax in: swap ``` 識別子マクロは、括弧を付けずに単体で使用しても機能するパターンマッチマクロです。例えば、valを(get-val)に展開する識別子マクロとして定義すると、(+ val 3)は(+ (get-val) 3)に展開されます ```ocaml > (define-syntax val (lambda (stx) (syntax-case stx () [val (identifier? (syntax val)) (syntax (get-val))]))) > (define-values (get-val put-val!) (let ([private-val 0]) (values (lambda () private-val) (lambda (v) (set! private-val v))))) > val 0 > (+ val 3) 3 ``` valマクロはsyntax-caseを使用しており、より強力なマクロを定義することができます。これについては、「パターンと表現の混合:syntax-case」のセクションで説明します。マクロを定義するには、ラムダで syntax-case を使用し、そのテンプレートを明示的な syntax コンストラクタでラップする必要があることを理解しておけば十分です。最後に、syntax-case 節では、パターンの後に追加のガード条件を指定できます。 val マクロでは、val が括弧付きで使用されないように identifier? 代わりに、マクロは構文エラーを発生させます。 ```ocaml > (val) eval:8:0: val: bad syntax in: (val) ``` --- ## 16.1.6 set! 変形機 上記のvalマクロでは、格納されている値を変更するためには、やはりput-val!を呼び出す必要があります。しかし、valに直接set! val が set! と共に使用されるときにマクロを呼び出すために、make-set!-transformer で代入トランスフォーマを作成します。また、set! を syntax-case literal list でリテラルとして宣言する必要があります ```ocaml > (define-syntax val2 (make-set!-transformer (lambda (stx) (syntax-case stx (set!) [val2 (identifier? (syntax val2)) (syntax (get-val))] [(set! val2 e) (syntax (put-val! e))])))) > val2 0 > (+ val2 3) 3 > (set! val2 10) > val2 10 ``` --- ## 16.1.7 マクロを生成するマクロ valやval2のような識別子がたくさんあって,それらをget-valやput-valのようなアクセサやミューテータ関数にリダイレクトしたいとします.このような場合、次のように書くことができます。 ```ocaml (define-get/put-id val get-val put-val!) ``` 当然ながら、define-get/put-idをマクロとして実装することができます。 ```ocaml > (define-syntax-rule (define-get/put-id id get put!) (define-syntax id (make-set!-transformer (lambda (stx) (syntax-case stx (set!) [id (identifier? (syntax id)) (syntax (get))] [(set! id e) (syntax (put! e))]))))) > (define-get/put-id val3 get-val put-val!) > (set! val3 11) > val3 11 ``` define-get/put-id マクロは、マクロを生成するマクロです。 --- ## 16.1.8 拡張例:Call-by-Reference関数 パターン マッチング マクロを使用して、Racket に一次参照呼び出し関数を定義する形式を追加できます。参照渡しの関数本体が形式引数を変異させる場合、その変異は関数の呼び出しで実際の引数として与えられる変数に適用されます。 例えば、define-cbrがdefineと同じように参照呼び出し型の関数を定義している場合、次のようになります。 ```ocaml (define-cbr (f a b) (swap a b)) (let ([x 1] [y 2]) (f x y) (list x y)) ``` は、(2 1)を生成します。 ここでは,引数の値を直接与えるのではなく,関数呼び出しに引数のアクセッサとミューテータを与えることで,参照渡しの関数を実装します.具体的には、上記の関数fに対して、次のように生成します。 ```ocaml (define (do-f get-a get-b put-a! put-b!) (define-get/put-id a get-a put-a!) (define-get/put-id b get-b put-b!) (swap a b)) ``` また、関数呼び出し(f x y)を次のようにリダイレクトします。 ```ocaml (do-f (lambda () x) (lambda () y) (lambda (v) (set! x v)) (lambda (v) (set! y v))) ``` 明らかに、define-cbr はマクロ生成マクロであり、f を do-f の呼び出しに展開するマクロにバインドします。つまり、(define-cbr (f a b) (swap a b)) は次のような定義を生成する必要があります。 ```ocaml (define-syntax f (syntax-rules () [(id actual ...) (do-f (lambda () actual) ... (lambda (v) (set! actual v)) ...)])) ``` 同時に、define-cbrはfの本体を使ってdo-fを定義する必要がありますが、この2番目の部分は少し複雑なので、そのほとんどをdefine-for-cbrヘルパーモジュールに委ね、define-cbrを簡単に書くことができるようにしています。 ```ocaml (define-syntax-rule (define-cbr (id arg ...) body) (begin (define-syntax id (syntax-rules () [(id actual (... ...)) (do-f (lambda () actual) (... ...) (lambda (v) (set! actual v)) (... ...))])) (define-for-cbr do-f (arg ...) () ; explained below... body))) ``` 残る課題は、define-for-cbr を定義して、次のように変換することです。 ```ocaml (define-for-cbr do-f (a b) () (swap a b)) ``` を上記の関数定義do-fに変換することです。ほとんどの作業は、aとbの各引数に対してdefine-get/put-id宣言を生成し、それをボディの前に置くことです。通常、パターンやテンプレートの中の...では簡単な作業ですが、今回は問題があります。get-aとput-a!という名前と、get-bとput-b!という名前を生成する必要がありますが、パターン言語では既存の識別子に基づいて識別子を合成する方法がありません。 しかし、レキシカルスコープを使えば、この問題を回避することができます。トリックは、関数の各引数に対してdefine-for-cbrの展開を一度ずつ繰り返すことです。define-for-cbrが引数リストの後に一見無駄な()を付けて始まるのはそのためです。これまでに見たすべての引数と、それぞれに生成されたgetとputの名前を、処理が残っている引数に加えて記録しておく必要があります。すべての識別子を処理した後は、必要な名前がすべて揃うことになります。 以下にdefine-for-cbrの定義を示します。 ```ocaml (define-syntax define-for-cbr (syntax-rules () [(define-for-cbr do-f (id0 id ...) (gens ...) body) (define-for-cbr do-f (id ...) (gens ... (id0 get put)) body)] [(define-for-cbr do-f () ((id get put) ...) body) (define (do-f get ... put ...) (define-get/put-id id get put) ... body)])) ``` 順を追って、以下のように展開が進みます。 ```ocaml (define-for-cbr do-f (a b) () (swap a b)) => (define-for-cbr do-f (b) ([a get_1 put_1]) (swap a b)) => (define-for-cbr do-f () ([a get_1 put_1] [b get_2 put_2]) (swap a b)) => (define (do-f get_1 get_2 put_1 put_2) (define-get/put-id a get_1 put_1) (define-get/put-id b get_2 put_2) (swap a b)) ``` get_1、get_2、put_1、put_2の「添え字」は、マクロエキスパンダによって挿入され、字句の範囲を保持します。これは、define-for-cbrの各反復によって生成されたgetが、異なる反復によって生成されたgetを束縛してはならないからです。つまり、本質的にはマクロ展開ツールを騙して新しい名前を生成させているのですが、このテクニックは、自動レキシカル スコープを備えたパターン ベースのマクロの驚くべき力の一部を示しています。 最後の式は最終的に次のように展開されます。 ```ocaml (define (do-f get_1 get_2 put_1 put_2) (let ([tmp (get_1)]) (put_1 (get_2)) (put_2 tmp))) ``` これは,名前を付けて呼び出す関数fを実装したものです。 要約すると、パターンベースの小さなマクロdefine-cbr、define-for-cbr、define-get/put-idの3つだけで、Racketに参照渡しの関数を追加することができます。