Island Life

< "The Martian", "Revival" | Resourcefulness >

2015/09/06

Schemeから可変長引数を引き算したら

https://twitter.com/___yuni/status/640394172407656448

@___yuni: 割りと前から言ってる気がするけどschemeから可変長引数削除したらいろんなことがめっちゃ綺麗になるから削除したい…

Alice: 賛成さんせー。プログラムをメタに扱うときに可変長引数があると色々面倒なのよね。

Bob: うーん、そうしたら不定長コンテナのコンストラクタはどうなるのさ。 (list 1 2 3) とか (vector 1 2 3 4) って書けるのは listvectorが可変長引数を取れるからだよね。

Alice: そんなの '(1 2 3) とか '#(1 2 3 4) でいいじゃない。

Bob: いやクオートしたら全体がリテラルになっちゃうよ。中で変数展開したいとき どうするのさ。(list x (+ x 1))'(x (+ x 1)) じゃ 意味が違うでしょう。

Alice: ふっふっふ。私たちにはquasiquoteがあるのよ。中で展開したければこう書けるわ。

(define x 10)

`(,x ,(+ x 1))  => (10 11)
`#(,x ,(+ x 1)) => #(10 11)

Bob: 不定長なのはリストとベクタだけじゃないよ。u8vectorとかどうするのさ。 `#u8(,x ,(+ x 1)) とは書けないんだよ。だってそれはリーダの定義により (quasiquote #u8((unquote x) (unquote (+ x 1)))) と等価だけど、 リテラル #u8 の要素には数値しかありえないんだから。readの時点で エラーになっちゃう。

Alice: そもそもリテラルとコンストラクタを分ける意味ってあるのかしら。 #u8(1 2 3) ならリテラルデータだし、 #u8(x (+ x 11)) なら実行時に計算してu8vectorを返す式ってことにできないかな。 いやxが定数だってコンパイル時に分かるんなら計算してリテラルにしちゃっても良いんだけど。 つまりプログラマがわざわざ意識して使い分ける必要なくない? それで良いなら、 リスト以外についてはコンストラクタをどうするかって悩む必要ないわよね。 リストについてはquasiquoteで解決と。

Bob: それは綺麗な設計だけど、Schemeのようにmutableなデータがある場合は うまくいかないよ。'#(1 2 3) は変更不可なリテラルだけど (vector 1 2 3) は後でvector-set!されるかもしれない。 この二つを区別できる必要がある。

Alice: だめか… 仕切り直して、不定長コンストラクタ用の専用構文をひとつ 作るってのはどう? 例えばHaskellみたいに[1 2 3] がリストコンストラクタなの。 (let1 x 10 [x (+ x 1)]) => (10 11) って するわけよ。リストさえ構築できれば、他のデータ型はコンストラクタがリストを取るように すればいいだけよね。こんなふうに。

(->vector [1 2 3]) => #(1 2 3)
(->u8vector [1 2 3]) => #u8(1 2 3)

Bob: その場合、'[x (+ x 1)] は何になるんだよ。

Alice: クオートされてるんだから [x (+ x 1)] でしょう?

Bob: いやREPLに打ち込んだら確かにそう返ってくるかもしれないけどさ、 その[...]って、具体的にはどういうオブジェクトなのさ。リストじゃないよね。 (list? '[x (+ x 1)] => #t にしちゃったら (list? '(x (+ x 1)) と区別できないし。

Alice: うーん…

Bob: それに、プログラム的に[...]になるような構造を生成したい時は どうする? 例えばリスト(a b c d)を受け取って [(a b) (c d)]というプログラム片を生成したい、とかさ。 マクロ書く時にこういうこと良くあるよね。

Alice: そうねえ、そもそも [...](...) と二種類あるのが 良くないんだとしたら? もういっそのこと [...]をリストってことにしちゃいましょうよ。

(car [1 2 3]) => 1
(cdr [1 2 3]) => [2 3]
(cons 1 [2 3]) => [1 2 3]

ほら、なんか良さげ。 []の中は評価されるけど全体をクオートすればリストリテラルってことにもできる。

(define x 10)
[x (+ x 1) 'x]  => [10 11 x]
'[x (+ x 1) 'x] => [x (+ x 1) 'x]

Bob: えーと、それじゃ '(car [1 2 3])は何になるの? [1 2 3]をリストにしたなら、'(car ...) はリストじゃないよね。じゃあ何?

Alice: ぐぬぬ。じゃ、じゃあ(...)はリストのままでいいわ。 [...]をベクタってことにしましょう。さっきと逆に、リストのコンストラクタが ベクタを取ることにするの。

(vector? [1 2 3])  => #t
(->list [1 2 3]) => (1 2 3)
(->u8vector [1 2 3]) => #u8(1 2 3)

Bob: まあ機能しなくはないけど、不定長リストの構築にいちいち(->list [...])って 書かなきゃいけない、ってのはなんだか面倒だねえ。REPLでmapを試すのにもいちいち こう書くってことだろう。

(map (^a (+ a 1)) (->list [1 2 3]))

Alice: mapがベクタも取れるようにすればいいわ。 ベクタだけじゃなくて不定長のコンテナはぜんぶ統一して使えるようにすれば。 そういうコンテナの最初の要素を取るfirstと残りの要素のコンテナを返す restをオーバーロードしておけば後はそれで全部書けるでしょう。

Bob: そういう言語知ってるよ。Clojureっていうんだ。


Claude: (まあそのClojureでさえ)(不定長引数は捨ててないけれどね)

(Lisp系言語は(同図象性によって)プログラムはメタレベルでデータとなる)(それは(本質的に)不定長のリストだ)(プログラムの構築を(メタレベルの)プログラムで行おうとすると、不定長リストの構築は避けて通れない。)

(回避方法は色々考えられるけど、どこかで辻褄合わせが必要になるんだよね)

Tags: Programming, Scheme

Post a comment

Name: