- def と Symbol と Var の話
- def と Symbol と Var の話 2:なぜ Symbol と Var が独立して存在するのか
- def と Symbol と Var の話 3 :値の変更(この記事)
前回の続きです。
defは、Symbol --> Var --> Valueという数珠つなぎを構成します。
なので、その値(というか評価結果)を変更する方法もいくつかに分けて考えられるのではないかと思います。
Valueオブジェクト自体の中身を変更する。Var --> Valueの束縛を変更する。Symbol --> Varの対応を変更する。
上記の視点でシンボルの評価結果を変更する方法を調べて整理してみました。
以下の記事を参考にしました。
- 関数を動的に定義する関数をつくる - tnoda-clojure
- symbols - In Clojure, how to define a variable named by a string? - Stack Overflow
- alter-var-root - clojure.core | ClojureDocs - Community-Powered Clojure Documentation and Examples
- intern - clojure.core | ClojureDocs - Community-Powered Clojure Documentation and Examples
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関数を使うと、SymbolとVarの 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に対応する内容を変更する方法を調べました。
- bind(束縛)されているオブジェクトの内容自体を変更する。*2
alter-var-rootを用いて bind を変更する。変更はアトミック。
変更は関数で指定し、変更前の値に応じたロジックを渡すことが可能。defを用いて bind を変更する。変更はアトミックではない。internを用いて intern を変更する。