読者です 読者をやめる 読者になる 読者になる

(-> % read write unlearn)

All opinions expressed are solely my own and do not express the views or opinions of my employer.

def と Symbol と Var の話 2:なぜ Symbol と Var が独立して存在するのか

clojure

前回のdef と Symbol と Var の話の続きです。

VarSymbolの役割、defにおける挙動はなんとなく分かってきました。 では、なぜそんなややこしい*1設計になっているのでしょうか。 Javaとかには「変数名」みたいな型はないですよね。 リフレクションするときも文字列でフィールド名やメソッド名を指定します。

当然Clojureもいたずらに複雑にしたいわけではなくて、ちゃんと目的と意味があります。

lisp - Why does Clojure distinguish between symbols and vars? - Stack Overflowより、回答者levandさんのなぜClojureがSymbolやVarといった概念を導入しているのかについての説明がとても丁寧で詳しかったです。ちょっと長いですが「But why?」以降を引用します。

So why this extra concept of names as distinct from variables, rather than conflating the two as most languages do?

For one thing, not all symbols are bound to vars. In local contexts, such as function arguments or let bindings, the value referred to by a symbol in your code isn't actually a var at all - it's just a local binding that will be optimized away and transformed to raw bytecode when it hits the compiler.

Most importantly, though, it's part of Clojure's "code is data" philosophy. The line of code (def x 1) isn't just an expression, it's also data, specifically a list consisting of the values def, x, and 1. This is very important, particularly for macros, which manipulate code as data.

But if (def x 1) is a list, than what are the values in the list? And particularly, what are the types of those values? Obviously 1 is a number. But what about def and x? What is their type, when I'm manipulating them as data? The answer, of course, symbols.

And that is the main reason symbols are a distinct entity in Clojure. In some contexts, such as macros, you want to take names and manipulate them, divorced from any particular meaning or binding granted by the runtime or the compiler. And the names must be some sort of thing, and the sort of thing they are is symbols.

一応、訳してみたんですが、色々間違ってそうです。。最後の1文とか特によく分からなかったです。

いったいなぜ他の多くの言語がしているように2つの概念をまとめておくのではなく、「変数」とは区別して「名前」という余分な概念を導入したのか。

1つの理由として、全ての symbol が必ずしも var に束縛されるわけではない、ということがある。関数の引数や let 束縛のようなローカル・コンテキストにおいては、値はコード内の symbol によって参照され、参照されるのは var ではない。つまり、この場合の参照は単なるローカルの束縛なので、これらはコンパイル時に最適化され生のバイトコードに変換される。

最も重要なことだが、この設計・概念は「コードはデータである」というClojureの哲学を構成しているのだ。(def x 1)という1行のコードは、単なる式というだけではない。(def x 1)、はデータ - もっと言うならdef, xそして1からなるリストなのだ。これはとても重要なことだ。とりわけ、コードをデータとして扱い操作するマクロにとってはなおさら。

しかし、もし(def x 1)がリストだとしたら、リスト内のそれぞれの値はいったい何なのだろうか。もっと言うと、このリストのそれぞれの値の型は何になるのだろうか。3つのうち1の型は明らかに数値である。しかし、defxどうだろうか。このdefxをデータとして操作しているとき、それらの型は何になるのだろう。もちろん、この答えこそが symbol だ。

以上が、Clojureにおいて symbol が独立したエンティティであることの主な理由である。マクロのようなコンテキストでは、コンパイル時や実行時に付与される特定の意味や束縛とは分離して、それらの名前を取得し操作したい。そしてそのためには、それら名前はなんらかの「もの」の一種でなければならず、それが symbol だ。


一言で言うと、マクロで名前(変数名)を操作したいからそのためには名前もオブジェクトじゃないといけないね、ということみたいです。こういうことを知っていると、メタプログラミングをやるときの助けになってくれそうですね。

letや関数の仮引数がvarを使わないっていうのは本当なんでしょうか。感覚的には、Javaのローカル変数みたいな扱いだからなんとなくしっくりきます。 あと、そうだとして、どうしてそれが「変数名」にSymbolオブジェクトを導入した理由になるのかいまいちよく分かりません。。 そもそもちゃんと読めているのか不安。。

*1:と自分は思った