def と Symbol と Var の話 3 :値の変更
- 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")
'date
Symbolが"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
'date
Symbolの intern 先を help フォームに付け替えたので、date
で help フォームが実行されるようになりました。
まとめ
def
で作成したSymbol
に対応する内容を変更する方法を調べました。
- bind(束縛)されているオブジェクトの内容自体を変更する。*2
alter-var-root
を用いて bind を変更する。変更はアトミック。
変更は関数で指定し、変更前の値に応じたロジックを渡すことが可能。def
を用いて bind を変更する。変更はアトミックではない。intern
を用いて intern を変更する。