ふたつの begin
プログラミング言語 Scheme の中で比較的頻繁に現れる構文として begin
がある。 複数の式をひとつにまとめる構文で、おそらくは if
と共に使う場合が多いのではないだろうか。
(if foo
(begin (display "Hello, world") (newline)))
R5RS だとちょっとあやふやでわかり難いのだが、 R6RS と R7RS では begin
は二種類あることが明記されている。 begin
を使用する場所によって意味が切り替わるのだ。
- 上述の例のように、通常の式として表われる場合
-
<body>
部やトップレベルなどに表われる場合
単純に複数の式をひとつにまとめるのはわかりやすい例で、 C などの文法で言うところの {
}
と同じような感覚だろう。
begin
の継ぎ合わせ
特殊な動作をするのは <body>
部やトップレベルなどに現われた場合だ。 <body>
部の詳細な定義はここでは述べないが、たとえば lambda
構文で引数の次の場所はそのひとつだ。
(lambda <formals> <body>)
<body>
部に現れる begin
は上位フォームへの継ぎ合わせ (splicing) がおこなわれる。
つまり、たとえば
(lambda ()
(display "Hello,")
(begin (display "World")
(newline)))
という式があった場合、 begin
の内側はその外側にあるのと同じようにふるまうので、
(lambda ()
(display "Hello,")
(display "World")
(newline))
と同じことになる。 要するに、 begin
で囲まないのと同じだということだ。
begin
の継ぎ合わせが必要な理由
あってもなくても同じならどういうときに使うのかと思うかもしれない。 それは主にマクロのためだ。 マクロはひとつの式をひとつの式に変換するが、出力結果を複数の式にしたいこともある。 そういったときに見掛け上をひとつの式にするために begin
でくるむのである。
たとえば、同じ値に束縛をたくさん作りたいようなことがあったとき、
(define a 0)
(define b 0)
(define c 0)
という風に並べるのは面倒であるから、一度に指定できるようなマクロを作ろうとするとこうなる。
(define-syntax define-zero
(syntax-rules ()
((_ id ...)
(begin (define id 0) ...))))
(define-zero a b c) ;; 使用例
(display a) ;; a は 0 に束縛されている
もしも、このときに begin
が C などの {
}
と同じようにスコープを形成してしまうのだと、マクロで定義系の構文を生成するのは強い制限がかかることになってしまう。 begin
が上位フォームに継ぎ合わされる (実質的に begin
で囲まないのと同じになる) という、一見して奇妙にも思える規則はこうして有用に役立てられるのである。
let-syntax
と letrec-syntax
の継ぎ合わせ
継ぎ合わせが起こるのは begin
だけではない。 R6RS の let-syntax
や letrec-syntax
もそうだ。 ただ、 R5RS や R7RS ではこれらの構文では継ぎ合わせは起こらないという違いがある。
このような式を書いた場合、
(let ((foo 'a))
(let-syntax ()
(define foo 'b)
'dummy-for-r7rs)
foo)
R6RS では 'b
になるが、 R5RS や R7RS では 'a
となるという違いになって現われる。
とても地味な違いだが、凝ったマクロを考えるときには R6RS の方がやりやすいと私は感じている。 継ぎ合わせをしてくれる場合には継ぎ合わせが起こって欲しくない箇所を適当に let
でくるめば済むが、継ぎ合わせが起こらない分には継ぎ合わせを引き起こすことは出来ないので使い分けられないからだ。