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

(-> % read write unlearn)

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

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

clojure

前回の続きです。

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 している場合は不可能