見出し画像

Retro-gaming and so on

RE: プログラミング日記 2021/11/04

星田さんの記事に対するコメント。

全然分からんw! と言うか書式が書かれてない気がする!見落としかも知れんが・・

単純には

(define-syntax マクロ名
 (syntax-rules (キーワード ...)
  ((式A 式Aの変換形)
  (式B 式Bの変換形)
  ...)))

ですね。
式A、式B、を「パターン」として見做して、適合した時変換後の式のように「並べ替え」ます。
技術的にこういうのをパターンマッチングと言うんですが、パターンマッチを利用して任意の式変換をする、と言うのがSchemeのマクロ、syntax-rulesです。

「独習Scheme~」を見ると・・Syntax-rulesどこいった?謎が深まる・・

あははははwwww。
そう、だから「独習Scheme〜」って古い、って言ったでしょ(笑)?

独習Scheme三週間は1998年に書かれた文書です。1998年にはR5RSが決まってますが、事実上 IEEE Scheme(※)、つまりR4RS用文書なのです。
そしてR4RS時点ではSchemeのマクロはまだ「オプション」で、どうなるか分からんかった(正式にマクロが仕様に含まれたのはR5RS以降)。

と言うわけで、この時点でのSchemeの「マクロ機能」ってのは実装依存だったんです。それで大方のScheme処理系は「問題がありながらも」ANSI Common Lispスタイルのマクロを採用していたのです。

一応、非推奨ですが、後方互換性の為、RacketでもいまだANSI Common Lispスタイルのマクロを使うことが出来ます。(require compatibility/defmacro)すれば良い。

(require compatibility/defmacro)

(define-macro (when test . branch)
 `(if ,test (begin ,@branch) #f))

更に「お気楽Scheme~」で調べると・・・ふんふん引数を取らない?

いや、「引数を取らない」のではなく、「引数を評価しない」です。
Haskellユーザーが言ってる通り、マクロは一種、遅延評価なのです。

ふーん・・・?まあ、作るコツは最終的に好みのS式が吐き出されるように考えると?

作るコツ、と言うか、それがむしろ「目的」です。
最初にSchemeのsyntax-rules()をやった方が良い、って言ったのは、そのアイディアに馴染むには丁度良いから、です。

あ、なるほど・・マクロには2種類あってSyntax-rulesは「健全なマクロ」って言われる書き方だったのか。この書式を見てもピンと来ないが・・

現行のSchemeのマクロ、Hygienic macrosを和訳で「健全なマクロ」「安全なマクロ」「衛生的マクロ」等と呼びます。
反面、ANSI Common Lisp(スタイル)のマクロを「レガシーマクロ」「コードマクロ」等と呼びますね。
何が健全で、何が衛生的なのか、と言うと、元々Schemeの仕様上、マクロを書く際に、マクロ内での変数が補足される・・・と言うかシンボルの「名前の衝突」が避けられないから、なんです。
詳しくはOn Lispに譲りますが、Schemeの仕様上、gensymが作れないのです。
いや、頑張れば実装上作れますよ?
ただ、Lispの良いトコ、ってのは「実装者が出来る事は使用者にも許す」と言う哲学があるトコで、他の言語仕様みたいに「実装者にこう実装して欲しいんだけど、ユーザにはさせねぇよ」ってトコが無いんです。だからシステム的なトコを「全て矛盾なく明らかにして」実装者にもユーザーにも伝えようとする。
だからANSI Common Lispはバカでかい仕様になってるわけですが(笑)。

ANSI Common Lispにはパッケージ、と言うシステムがあって、1パッケージに1名前空間、と言う約束があり、通常シンボルはパッケージに所属しています。
gensymって関数はパッケージ外にシンボル(変数)を作る、と言う機能です。
って事はその変数はどのパッケージからも参照出来ない。参照出来ない以上「唯一無二の」独立した変数だ、って事になる。
従って、マクロでこの変数を使う以上、どのパッケージからも参照不可能、って事になる。そうすると、どんな変数を使おうと、gensymで作られたマクロ内で定義された変数と衝突しようがないわけ、です。
これは非常に良く考えられたシステムで、上手いこと出来てるんですが。
生憎、Schemeにはパッケージがない。パッケージが無い以上、仕様上gensym出来ないんですよ。gensym出来ない以上、マクロを作ってもそこで使う変数は仕様上、常に衝突の可能性がある。つまり「安全じゃない」わけです。
これを解決する為に導入されたのが「健全なマクロ」なんですが・・・。
前にも書きましたが、事実上、Scheme本体とは「独立した」別の言語になっています。パターンマッチを駆使してる構文がイマイチピンと来ない最大の理由は、「構文がSchemeじゃない」から、ですね。全く別のパターンマッチ言語になっている。
あと、syntax-rulesは単純なマクロは書けますが、ANSI Common Lispで見るような「黒魔術染みた」複雑なマクロは書けません。あくまで簡単なパターン変換しか書けない。
R6RSで導入されたsyntax-caseならANSI Common Lisp並の超強力なマクロが記述出来ます(現時点、R7RS-smallには含まれていない)。
ただ、ぶっちゃけ、syntax-caseを使うくらいならANSI Common Lispスタイルのレガシーマクロの方が記述が簡単で(笑)、そんなのもあって、syntax-caseはイマイチ人気がないです。っつーかsyntax-caseを無理して使うくらいだったら、ぶっちゃけ、ANSI Common Lispを使った方がマシかもしんない、ってなくらいです。

いずれにせよ、Schemeのマクロは、ANSI Common Lispに移り、なおかつOn Lispを読む前提だとまずはsyntax-rulesを使えるだけで充分です。
構文は全く違いますが、「あるS式のパターンを別のパターンに変換する」と言う目的は一緒で、syntax-rulesはその「発想」を鍛える為「だけに」役立つでしょう。
だから構文の細かいとこは無視しても構いません。必要なのは「発想」なのです。
(逆に言うと、ANSI Common Lispのマクロで悩んだ時には「syntax-rulesじゃこう書いたから・・・」と言うのが、最初のうちは助けてくれる、って事です)

更に実際に必要に駆られて活用するって経験をしないと身につかん気がする(どれもだけど)。

この辺がマクロのムズいトコなんですよねぇ。
そして、マクロの第一原則、と言うのがある。
それはこれです。

  • マクロを書こうとするな
(笑)。
いや、マジで(笑)。良心的なLisp本だとどれもこう書いてるんだよな(笑)。
んで、マクロを書けるようになると、何でもマクロを書きたくなる。
でもそれは落とし穴だと言われています。

マクロを書く、って事は「構文を拡張出来る」と言う事です。
と言う事は「構文の決定権がある」って事なんで、事実上、「プログラミング言語を新しく設計するに等しい」のです。
こういうのを最近ではDSL(Domain Specific Language: ドメイン特化言語)とか呼んだりしますが、マクロはDSLの為の機能だと言って良い。
って事はLisp内で新しい言語を作れる、って事なんですが、これはある意味危険だ、と言えば危険なんですよ。「可能性」は広いけど、それはLispの一部なのかビミョーだとは言えます。可読性が落ちる確率も高い。

恐らく、マクロの最高傑作はANSI Common Lispのloopです。
「Lispのマクロだとこんな事が出来るのか!」なんですが、結果、どう見てもLispに見えない(笑)。

CL-USER> (loop for x in '(a b c d e)
        do (print x))

A
B
C
D
E
NIL
CL-USER>

「こんな事が出来る」反面、「見た目全然Lispじゃない」。要するにANSI Common Lispと言う仕様内での「loopの為のDSL」になってるんですよね。
感心できる「作品」ではあるでしょう。でもこんなんがアッチコッチにあったとしたら、もはやそれはLispプログラムに「見えない」。
個人で使う分にはいいんですが、他人がそのコードを読んだら地獄かもしんないです。

もう一つの欠点としては、マクロは高階関数の引数に取れない、のです。
例えば星田さんは前回applyをやりましたが、applyの引数にマクロは取れない。

> (apply and '(1 2 3 4 5))
. and: bad syntax in: and
>

つまり、マクロはファーストクラスオブジェクトではない、のです。

Lispには高階関数もあるし、ラムダ式もあるし、関数はおしなべてファーストクラスオブジェクトです。
従って、マクロはホント、ギリギリのギリギリになるまで出番がないです。

しかし「お気楽Scheme~」の説明は分かり易い(気がする)。

それはこの著者が、SHARP X68000ユーザーだったから、で(笑)、感性が近いんじゃないんですか(笑)?

Defineの理解も曖昧だったと判明w。最後に実行された処理の結果をその関数の実行結果として返すってのはちゃんと意識しないと・・(いまさら!?)

それ、なんで今まで意識しないで来れたのか、と言うのは、関数型プログラミングに徹してたから、です。
「暗黙のbegin」(あるいはANSI Common Lispでの「暗黙のprogn」)は関数型プログラミングをする以上あんま関係ないんですよ。
僕らは結局、なんだかんだ言ってこういう風に書いてきた。

(define (foo arg ...) ((((...)))))

暗黙のbeginが必要なのは次のような手続き型スタイルです。

(define (foo arg ...)
 ()
 ()
 ()
 ()
 ...)

明日はいよいよ「12歳~」を返却してLISP本4冊を借りる日、楽しみだ~

では、楽しんでください(笑)。

※: IEEEとは米国電気電子学会の事。ANSI(アメリカ規格協会)と同様、プログラミング言語の標準化規定機関の役割も果たしている(が審査はANSIに比すると緩いようだ)。
Schemeの仕様書もANSIに提案された事はあるが、どうやら否決された模様で、一方、「公式」仕様としてはIEEE版は確定してる。
故に、実のことを言うと、アメリカローカルの話ではあるが、ANSI Common Lispに対抗できる「公式仕様」はいまだにR4RSだと言う事になる。
また、ANSIに否決された故トラウマになったか、これ以降、Schemeの仕様書は「権威ある機関」に提案される事は無くなった。
余談だが、ANSI Common Lispユーザーが「Schemeには仕様がない」と言う批判を言うのはこれが原因で、Schemerは「権威のある仕様書」と「権威が無い仕様書」をごちゃまぜにして話をする傾向があり、ANSI Common Lispユーザーを増々苛立たせるのだ。
ANSI Common LispユーザーにとってはR5RS、R6RSもR7RSもアメリカの公的審査を通ってない仕様なので、「分けて話をすべき」なのだが、Schemerは一般にその辺をごちゃまぜにして話をする困ったちゃんなのである。

なお、ISO(国際標準化機構)と言う「もっと権威のある」機関の審査をパスした、例えばC言語やC++言語のユーザーにとってみれば

「ANSI Common Lispも所詮はアメリカのローカル仕様でしょ?」

ってな話なわけで、ISOをパスした他言語ユーザーから言ってみればANSI Common LispだろうとSchemeだろうと目糞鼻糞を笑う、の世界である。
そしてSchemeユーザーはいわゆるP言語(Perl、Python、Ruby)を「仕様が無い言語」として下に見る傾向があったが、このうち、RubyはISOの審査をパスしたので(2012年)、目出度くRubyユーザーはANSI Common Lisp及びSchemeを「下に見て」良くなったのである(笑)。
まさに因果応報である(笑)。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「RE: プログラミング日記」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事