<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.6;
letter-spacing: normal;
text-shadow: none;
word-wrap: break-word;
color: #444;
}
.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;}
.reveal h1, .reveal h2, .reveal h3 {color: #00a474;}
.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.5;}
.reveal p, .reveal li {padding: 0vw; margin: 0vw;}
.reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 1.0vw 1.5vw 0.5vw 1.5vw; background: #ffffe0; 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: #ddffff;
}
.reveal blockquote:before{
position: absolute;
top: 0.1vw;
left: 1vw;
content: "\f10d";
font-family: FontAwesome;
color: #00a474;
font-size: 3.0vw;
}
/* font size */
.reveal h1 {font-size: 5.0vw;}
.reveal h2 {font-size: 4.0vw;}
.reveal h3 {font-size: 2.8vw;}
.reveal h4 {font-size: 2.6vw;}
.reveal h5 {font-size: 2.4vw;}
.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>
/* 背景デザイン */
.reveal {
background-color:/*背景色*/
#ffffff;
}
.reveal h1 {padding: 3.0vw 0vw;}
@media screen and (max-width: 1024px) {
.reveal h2 {margin: -2.0vw 0 0 0; padding: 0.0vw 0vw 3.0vw 2.0vw; }
}
@media screen and (min-width: 1025px) and (max-width: 1920px) {
.reveal h2 {margin: -1.5vw 0 0 0; padding: 0.0vw 0vw 3.0vw 2.0vw; }
}
@media screen and (min-width: 1921px) and (max-width: 100000px) {
.reveal h2 {margin: -1.0vw 0 0 0; padding: 0.0vw 0vw 3.0vw 2.0vw; }
}
</style>
<style>
/* specific design */
.reveal h2 {
padding: 0 1.5vw;
margin: 0.0vw 0 2.0vw -2.0vw;
border-left: solid 1.2vw #00a474;
border-bottom: solid 0.8vw #9e9e9e;
}
.slider_fade {
position: relative;
}
/*Highlight <span><!-- .element: class="fragment highlight-red" -->red</span> <span><!-- .element: class="fragment highlight-blue" -->blue</span> <span><!-- .element: class="fragment highlight-green"-->green</span>*/
</style>
<!-- --------------------------------------------------------------------------------------- -->
#### 卒業研究スライド
# The Racket Guide<br>16 Macros
<br>
<br>
#### 2021/10/15
### @ariken
---
## マクロとは(自己解釈&翻訳)
<div class="box">
今までいろいろな関数を作ったが、いずれも引数を評価するものだった。
つまり、defineで定義できる関数は引数を評価するタイプで、シンタックス形式のように引数を評価しない関数を定義することはできない。Schemeでプログラミングする場合、ほとんどの処理はdefineで定義する関数で作ることができるが、シンタックス形式のように引数を評価しない関数を定義した方が便利な場合もある。このようなとき、役に立つのが「マクロ(macro)」である。
</div>
<div class="box">
マクロとは、元の形式を既存の形式に拡張する、関連する変換器を持つ構文形式のことである。別の言い方をすると、マクロはRacketコンパイラの拡張機能である。
</div>
<br>
<!-- .element: class="fragment" data-fragment-index="1" -->
<div style="margin-left: 30px;padding: 10px; margin-bottom: 10px; border: 2px solid #00a474;font-size:30px;font-weight: bold">
<u>マクロとは式の変換である。</u>
</div>
<!-- .element: class="fragment" data-fragment-index="2" -->
---
## define-syntax-rule
マクロを作成する最も簡単な方法は、 `define-syntax-rule` を使用すること。
```
(define-syntax-rule pattern template)
```
実行例として、2つの変数に格納されている値を交換する `swap` マクロを考えてみる。このマクロは、 `define-syntax-rule` を使用して次のように実装できる。
```
(define-syntax-rule (swap x y)
(let ([tmp x])
(set! x y)
(set! y tmp)))
```
----
## レキシカルスコープ
`swap` マクロを使って、 `tmp` と `other` という名前の変数を入れ替える。
```
(let ([tmp 5]
[other 6])
(swap tmp other)
(list tmp other))
```
上の式の結果は `'(6 5)` になる。
----
しかし、上記の式を展開すると
```
(let ([tmp 5]
[other 6])
(let ([tmp tmp])
(set! tmp other)
(set! other tmp))
(list tmp other))
```
このような式になり、この実行結果は `'(5 6)` になる。
問題は、 `swap` が使われている文脈での `tmp` と、マクロのテンプレートにある `tmp` が混同されていることである。
----
なぜ最初の実行では `'(6 5)` と正常に動作したのか?
Racketは、上記の `swap` の使用に対して、素朴な展開を行わないためである。その代わりに、次のように生成する。
```
(let ([tmp 5]
[other 6])
(let ([tmp_1 tmp])
(set! tmp other)
(set! other tmp_1))
(list tmp other))
```
となり、正しい結果の `'(6 5)` となる。
つまり、Racketのパターンベースのマクロは自動的にレキシカルスコープを維持するので、マクロの実装者は、関数や関数呼び出しと同じように、マクロ内の変数参照やマクロの使用について推論できる。<br>
[レキシカルスコープ](https://wemo.tech/904#index_id3)
---
## define-syntax 及び syntax-rule
`define-syntax-rule` 形式は、単一のパターンに一致するマクロを結合するが、Racketのマクロシステムは、同じ識別子で始まる複数のパターンに一致するトランスフォーマをサポートしている。このようなマクロを記述するには、プログラマーは、より一般的な `define-syntax` 形式と `syntax-rules` トランスフォーマ形式を使用する必要がある。
<br>
<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))` に展開される。
---
## マッチングシーケンス
`rotate` マクロは、2つまたは3つの識別子だけでなく、任意の数の識別子を使用できる。 `rotate` の使用と任意の数の識別子を一致させるには、==クリーネスター==のようなパターンフォームが必要です。 Racket のマクロパターンでは、星は .... のように記述される。
> シーケンス:「あらかじめ決められた順序で処理を行うこと」[シーケンス - 用語集 - EdTechZine(エドテックジン)](https://edtechzine.jp/glossary/detail/%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9#:~:text=%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20(%E3%81%97%E3%83%BC%E3%81%91%E3%82%93%E3%81%99)&text=%E3%80%8C%E3%81%82%E3%82%89%E3%81%8B%E3%81%98%E3%82%81%E6%B1%BA%E3%82%81%E3%82%89%E3%82%8C%E3%81%9F%E9%A0%86%E5%BA%8F,%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%E5%88%B6%E5%BE%A1%E3%80%81%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B7%E3%83%A3%E3%83%AB%E5%88%B6%E5%BE%A1%E3%81%A8%E3%82%82%E3%80%82&text=%E3%81%AA%E3%81%8A%E3%80%81JIS%E8%A6%8F%E6%A0%BC%E3%81%A7%E3%81%AF%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9,%E5%88%B6%E5%BE%A1%E3%80%8D%E3%81%A8%E5%AE%9A%E3%82%81%E3%81%A6%E3%81%84%E3%82%8B%E3%80%82)
> クリーネ閉包(くりーねへいほう、英: Kleene closure)は、形式言語とオートマトンの理論において、ある演算の繰り返しが「生成」するシンボルないし文字の列(文字列)の集合である。また、この繰り返しの単項演算子をクリーネスター(英: Kleene star)という。[(クリーネ閉包 - Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%8D%E9%96%89%E5%8C%85#:~:text=%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%8D%E9%96%89%E5%8C%85%EF%BC%88%E3%81%8F%E3%82%8A%E3%83%BC%E3%81%AD,%E8%8B%B1%3A%20Kleene%20star%EF%BC%89%E3%81%A8%E3%81%84%E3%81%86%E3%80%82)
----
`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 は、最初の値を最後の変数に直接移動させます。...パターンを使って、ヘルパーマクロを使って、より効率的な変形を実装することができます。
> Both versions of rotate so far are a bit inefficient, since pairwise swapping keeps moving the value from the first variable into every variable in the sequence until it arrives at the last one. A more efficient rotate would move the first value directly to the last variable. We can use ... patterns to implement the more efficient variant using a helper macro:[Pattern-Based Macros](https://docs.racket-lang.org/guide/pattern-macros.html)
----
```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のマッチ数は同じでなければならず、そうでなければマクロ展開はエラーで失敗する)。
---
## 識別子マクロ
マクロの定義では、開括弧の後に `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」](https://hackmd.io/AVXmTZ3fRKGcvTnASidqyQ)のセクションで説明している。マクロを定義するには、ラムダで `syntax-case` を使用し、そのテンプレートを明示的な `syntax` コンストラクタでラップする必要があることを理解しておけば十分である。最後に、 `syntax-case` 節では、パターンの後に追加のガード条件を指定できる。
`val` マクロでは、 `identifier?` 条件を使用して、 `val` が括弧と一緒に使用されないようにしています。代わりに括弧と一緒に使用すると、このマクロは構文エラーを発生させる。
```ocaml
> (val)
eval:8:0: val: bad syntax
in: (val)
```
---
## set! Trancefomers
上記の `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))]))))
```
実行すると以下のようになる。
```ocaml
> val2
0
> (+ val2 3)
3
> (set! val2 10)
> val2
10
```
---
## マクロを生成するマクロ
`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` マクロは、マクロを生成するマクロである。
---
## 拡張例: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に参照渡しの関数を追加することができる。
{"metaMigratedAt":"2023-06-16T12:21:43.858Z","metaMigratedFrom":"YAML","title":"Macroとは","breaks":true,"slideOptions":"{\"theme\":\"white\",\"controls\":true,\"progress\":true,\"slideNumber\":\"c/t\",\"center\":false,\"fragments\":true,\"help\":true,\"transition\":\"convex\",\"transitionSpeed\":\"default\",\"keyboard\":true,\"width\":\"93%\",\"height\":\"100%\",\"spotlight\":{\"enabled\":false}}","contributors":"[{\"id\":\"a0de3d6a-cf71-4961-a3bb-e324e7c21a77\",\"add\":17550,\"del\":2389}]"}