(-> % read write unlearn)

My writings on this area are my own delusion

def と Symbol と Var の話 3 :値の変更

  1. def と Symbol と Var の話
  2. def と Symbol と Var の話 2:なぜ Symbol と Var が独立して存在するのか
  3. def と Symbol と Var の話 3 :値の変更(この記事)

前回の続きです。

defは、Symbol --> Var --> Valueという数珠つなぎを構成します。 なので、その値(というか評価結果)を変更する方法もいくつかに分けて考えられるのではないかと思います。

  1. Valueオブジェクト自体の中身を変更する。
  2. Var --> Valueの束縛を変更する。
  3. Symbol --> Varの対応を変更する。

上記の視点でシンボルの評価結果を変更する方法を調べて整理してみました。

以下の記事を参考にしました。

1. オブジェクト自体を変更する

                         ここを変更
                          |
                          V
[Symbol] --> [Var] --> [Value]

上記の図のValueのオブジェクトが mutable (可変)であれば、単にそのフィールドを変更するだけです。簡単です。 例として、上記のValueとしてjava.util.Dateオブジェクトを使用します。*1

まず、defしてVarなどを作成します。

user=> (def date (java.util.Date.))
#'user/date

user=> date
#inst "2016-08-29T05:13:40.976-00:00"
[Symbol] --> [Var] --> [Value]
 'date                  Date("2016-08-29T13:13:40.976-00:00")

続いて bind 先のオブジェクト自体の内容を変更します。ここでは、Date#setTime(long)を使用しています。

user=> (.setTime date 0)
nil
user=> date
#inst "1970-01-01T00:00:00.000-00:00"
[Symbol] --> [Var] --> [Value]
 'date                  Date("1970-01-01T00:00:00.000-00:00")

ほぼ、Javaの世界だけの話ですね。

2. Varが何を束縛(bind)しているかを変更する

                   ここを変更
                    |
                    V
[Symbol] --> [Var] --> [Value]
 'date                  Date("1970-01-01T00:00:00.000-00:00")

Varの束縛はalter-var-rootで変更できます。第一引数に対象のVar、第二引数にどう変更するかの関数を渡します。

user=> (alter-var-root #'user/date (fn [d] (.getTime d)))
0

user=> date
0
[Symbol] --+    [Var] --> [Value]
 'date     |               Date("1970-01-01T00:00:00.000-00:00")
           |
           +--> [Var] --> [Value]
                           Long(0)

#'user/date(fn [d] (.getTime d))という匿名関数で変更しました。関数の引数のdには変更前のVarの束縛対象、つまりDateオブジェクトが渡されます。受け取ったDateオブジェクトから.getTimeした値が返り値となるので、0というLongの値に変更されています。

また、alter-var-root以外にもVarの束縛を変更する方法があります。 既に intern されたSymbolを指定してdefすると、既存のVarが再利用され束縛だけが変更されます。

user=>  (def date 123)
#'user/date

user=> date
123
[Symbol] --+
 'date     |
           |
           +    [Var] --> [Value]
           |               Long(0)
           |
           +--> [Var] --> [Value]
                           Long(123)

自分でVarを変更する機会に出くわしたことがないので、実際に使ったことがないですが、 「alter-var-rootによる変更はアトミックですがdefによる変更はアトミックではない」という注意点があるようです。

3. SymbolがどのVarを拘禁(intern)しているかを変更する

         ここを変更
          |
          V
[Symbol] --> [Var] --> [Value]
 'date                  Long(123)

intern関数を使うと、SymbolVarの intern 関係を変更することができます。

user=> (intern 'user 'date "foo")
#'user/date

user=> date
"foo"
[Symbol] --+    [Var] --> [Value]
 'date     |               Long(123)
           |
           +--> [Var] --> [Value]
                           String("foo")

'dateSymbolが"foo"文字列オブジェクトを束縛した新しいVarを intern するように変更されました。

intern関数は、新しいオブジェクトを指定すると新しいVarを作成して intern しますが、既存のVarを指定するとそのVarを利用するため特に新しいVarが作成されることはありません。

user=> (intern 'user 'date #'help)
#'user/date

user=> (date)
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

nil
[Symbol] --+
 'date     |
           |
           +    [Var] --> [Value]
           |               String("foo")
           |
           +--> [Var] --> [Value]
                #'help

'dateSymbolの intern 先を help フォームに付け替えたので、dateで help フォームが実行されるようになりました。

まとめ

defで作成したSymbolに対応する内容を変更する方法を調べました。

  1. bind(束縛)されているオブジェクトの内容自体を変更する。*2
  2. alter-var-rootを用いて bind を変更する。変更はアトミック。
    変更は関数で指定し、変更前の値に応じたロジックを渡すことが可能。
  3. defを用いて bind を変更する。変更はアトミックではない。
  4. internを用いて intern を変更する。

*1:これまでは例として String オブジェクトを使ってきましたが、 Java の Stringは immutable なので今回は例としてふさわしくないです。

*2:文字列などのようにいミュータブルなオブジェクトを bind している場合は不可能

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

  1. def と Symbol と Var の話
  2. def と Symbol と Var の話 2:なぜ Symbol と Var が独立して存在するのか(この記事)
  3. def と Symbol と Var の話 3 :値の変更

前回の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:と自分は思った

def と Symbol と Var の話

  1. def と Symbol と Var の話(この記事)
  2. def と Symbol と Var の話 2:なぜ Symbol と Var が独立して存在するのか
  3. def と Symbol と Var の話 3 :値の変更

GPソフト Wiki - ClojureのVarの記事を読んでいて、 自分が、Clojureのdefの挙動やSymbol, Varといった言葉の意味をよく理解していないことに気付きました。この記事や以下の記事を参考にしつつ自分なりに一度整理してみます。

clojure.lang.Varについて知っていること、分かったこと

  • なんらかの「もの」を表すオブジェクト。名前はない(名前の機能はSymbolが独立して担当)。

  • 可変(mutable)。Clojureで可変なのは他に、RefsAgentsAtomsだけ。*1

  • defによって、なんらかのオブジェクトを束縛した状態で作成され、名前空間にinternされる。

  • ClojureVarに似た機能をJavaのなかに探すとしたら「変数」だろうか。

  • Varという名前は、variableからきていると思っている。

clojure.lang.Symbolについて知っていること、分かったこと

  • Rubyのシンボルとはかなり違う。Rubyのシンボルに近い用途としてClojureでよく使うのはKeyword

  • 「もの」の名前を表すオブジェクト。

  • ClojureSymbolに似た機能をJavaのなかに探すとしたら「変数名」だろうか。Javaの場合は「変数」と「変数名」を切り離して捉えたりはあまりしないが、Clojureではこの「変数名」の役割をSymbolというオブジェクトに独立して担わせている。

  • Symbolを評価すると、まずその名前に対応するVarを調べ、そしてVarが束縛する値になる。対応するVarが見つからない場合は、RuntimeExceptionが投げられる。

user=> a

CompilerException java.lang.RuntimeException: Unable to resolve symbol: a in this context, compiling:(/private/var/folders/sf/lyhc_rw16xlckx_yj0gyh5c40000gn/T/form-init1896185904742541191.clj:1:13012)
user=> (def a 123)
#'user/a
user=> a
123
  • SymbolインターフェイスIFnを実装している。つまり関数として振る舞うことができる。
    具体的には、コレクションを1つ引数に取り、そのSymbolのキーに対応するバリューを返す。
user=> ('a {'a 10 'b 20})
10

defについて知っていること、分かったこと

defによるbind(束縛)

オブジェクトを束縛する単純なコードです。

user=> (def foo "hoge")
#'user/foo

このコードでは

  1. まず、"hoge"というStringオブジェクトが生成される。*2
  2. 次に、Varオブジェクトが作成される。
  3. このとき、このVarオブジェクトは"hoge"オブジェクトをbind(束縛)する。
  4. そして、fooという名前のSymbolオブジェクトが作成され、
  5. それから、このVarオブジェクトをintern(拘禁)する。
    具体的には、SymbolVarマッピングを、Namespace名前空間)に登録している。
  (4) Symbol                    (2) Var                   (1) String
+------------+               +------------+             +------------+
|            |               |            |             |            |
|    foo     |-------------->| 0x4e082766 |------------>|    hoge    |
|            |  (5) intern   |            |  (3) bind   |            |
+------------+               +------------+             +------------+

            Namespace
    +-------------------------------+
    |     +-----------------------+ |
    |     | [Symbol] :   [Var]    | |
    |     | ...      : ...        | |
    |     | ...      : ...        | |
    |     | foo      : 0x4e082766 | |  (5) intern
    |     | ...      : ...        | |
    |     +-----------------------+ |
    |                               |
    +-------------------------------+
  • 上記の図のとおり、internの対応はSymbolオブジェクトが保持しているわけではなくて、Namespace名前空間)オブジェクト内のフィールドにマップで保持されています。面倒なので、以降の図ではマッピングの図は省略して単にSymbolからVarに矢印を引きます。

このような挙動なので、多くの他の言語ではvar x = 1の結果を「xは1」と表現するのに対して、Clojureでは同様の(def x 1)というコードは「シンボルxと名前が付けられたvarの値が1」と表現するとやや厳密になります。

defによるunboudなVar

defの第二引数はVarがbindする初期値です。これを省略すると何もbindしない(unboundな)状態のVarがinternされます。 *3 *4

user=> (def bar)
#'user/bar
    Symbol                         Var                   Var$Unbound
+------------+               +------------+             +------------+
|            |               |            |             |            |
|    foo     |-------------->| 0x4e082766 |------------>|            |
|            |    intern     |            |    bind     |            |
+------------+               +------------+             +------------+

この状態でbarを評価するとunboudなVar$Unboundというオブジェクトが返ってきます。

user=> bar
#object[clojure.lang.Var$Unbound 0x62d56f27 "Unbound: #'user/bar"]

user=> (class bar)
clojure.lang.Var$Unbound

ちなみに、自分はこのときのVarは何もbindしていないのかと思っていたのですが、実際には上記の図のようにclojure.lang.Var$Unboundクラスのオブジェクトをbindしているようです。 このclojure.lang.Var$Unboundは、clojure.lang.Varクラス内に書かれたインナークラスであり*5、コードを読むとclojure.lang.Varへの参照のフィールドを持っています。

上記の図よりも下記の図のほうが、実際の実装により近いかもしれません。

    Symbol                         Var                   Var$Unbound
+------------+               +------------+             +------------+
|            |               |            |------------>|            |
|    foo     |-------------->| 0x4e082766 |             |            |
|            |    intern     |            |<------------|            |
+------------+               +------------+             +------------+
(1) そのSymbol自体の取得

そのSymbolオブジェクト自体を取得するにはquote特殊フォームを使います。

user=> (quote foo))
foo
これを取得する
      |
      |
      V

    Symbol                         Var                      String
+------------+               +------------+             +------------+
|            |               |            |             |            |
|    foo     |-------------->| 0x4e082766 |------------>|    hoge    |
|            |    resolve    |            |             |            |
+------------+               +------------+             +------------+

'リーダー・マクロでも同じ効果が得られます。

user=> 'foo
foo

symbol特殊フォームを使うとSymbol名を文字列で指定して取得できます。

(symbol "foo")
foo

'fooの型を確認すると、確かにclojure.lang.Symbolです。

user=> (class 'foo)
clojure.lang.Symbol
(2) intern(拘禁)されているVarの取得

Varオブジェクト自体を取得するにはvar特殊フォームを使います。

user=> (var foo)
#'user/foo
これを取得する
             -----------------------¬
                                    |
                                    V

    Symbol                         Var                      String
+------------+               +------------+             +------------+
|            |               |            |             |            |
|    foo     |-------------->| 0x4e082766 |------------>|    hoge    |
|            |               |            |             |            |
+------------+               +------------+             +------------+

#'リーダー・マクロでも同じ効果が得られます。

user=> #'foo
#'user/foo

#'fooの評価結果の型を確認すると、確かにclojure.lang.Varです。

user=> (class #'foo)
clojure.lang.Var
(3) bind(束縛)されている値の取得

defでbindした"hoge"オブジェクトを取得するには、fooというSymbolを評価します。

user=> foo
"hoge"
  1. まず、fooというSymbolに対応するVarを調べます。これを「resolve(解決)する」と表現するようです。
  2. 対応するVarが見つかったら、そのVarがbindする値を取得する。
  3. この場合、得られる値はhoge文字列オブジェクトです。
これを取得する
             --------------------------------------------------¬
                                                               |
                                                               V

    Symbol                         Var                    (3) String
+------------+               +------------+             +------------+
|            |               |            |             |            |
|    foo     |-------------->| 0x4e082766 |------------>|    hoge    |
|            |  (1) resolve  |            |     (2)     |            |
+------------+               +------------+             +------------+

fooの評価結果の型を確認すると、確かにjava.lang.Stringです。

user=> (class foo)
java.lang.String

また、@リーダーマクロはVarにbindされている値を取得できます。したがって、@#'の2つのリーダー・マクロを組み合わせて"hoge"を得ることもできます。(この場合は読み辛いだけですが。)

user=> @#'foo
"hoge"

まとめ

  1. defは第二引数の値を第一引数のSymbolにintern(拘禁)する。
  2. Symbolを評価すると、そのSymbolにintern(拘禁)されているVarにbind(束縛)されている値が返る。

そして、長くなってきたので続きます。


(追記2016年08月30日)
こちらのまとめもとても勉強になりますので、ぜひご参照を。

togetter.com

*1:Javaのフィールドも含めるべき?

*2:もちろん、"hoge"Stringオブジェクトが作られるのは、正確にはdefの効果ではなく、単に"hoge"というリテラル表現の結果です。

*3:bind(束縛する)の否定の動詞が、unbind(解放する)です。そして、それらの過去形(過去分詞形)がそれぞれ bound と unbound です。

*4:barというシンボルに既になんらかVarがinternされていたら(つまり、barという名前が既に何かでdefされていたら)、defは何もしません。

*5:staticな場合ってインナークラスって言わないんでしたっけ?ごめんなさい、ClojureだけでなくJavaもぼろぼろです。