erlang:dets の動作を調べる(その1)
以前紹介した付箋webを開発する際に、Erlang のストレージシステムの一つである dets を使用しました。
この dets の基本的な使い方と、動作についてよくわからない所があったので、実験とその結果を書いてみたいと思います。
detsのデータベースを作る
Erlangを起動してdetsファイルを作ります。
~/work$erl
Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [kernel-poll:false]
Eshell V5.5.5 (abort with ^G)
1> {ok,Ref}=dets:open_file(dets1.file,[]).
{ok,'dets1.file'}
ファイルができました。
~/work$ls -l total 8 -rw-r--r-- 1 klab klab 5432 Sep 21 15:28 dets1.file ~/work$
open_file()の引数(dets1.file)はatomですが、第2引数で指定するリストで指 定しなければatomがそのままファイル名になります。 大文字で始まるファイル名の場合は、' 'で囲めばatomとして扱ってくれます。 ファイル名をatomとは別に指定したいときは[{file,"FILENAME"}]のようなオ プションを指定します。
成功すると{ok,Ref}のタプルを返します。Refにはatomが入ります。オープン 後detsを操作するのはRefでもatomのdets1.fileでも同じです。 作成されたdetsの情報はinfo()で調べられます。
2> Ref. 'dets1.file'
3> dets:info(dets1.file).
[{type,set},{keypos,1},{size,0},{file_size,5752},{filename,"dets1.file"}]
テスト中に文の間違いなどでエラーを起こすとatomが無効にされるので、時々 info()でチェックしたほうがいいでしょう。 クローズします。
5> dets:close(Ref). ok
データを挿入します。
(何度かErlangを起動し直したり、文書の順番を整理しているのでプロンプト
の番号は順番になっていません。)
2> dets:insert(dets1.file,{neko,{"TAMA","4"}}).
ok
3> dets:insert(dets1.file,{inu,{"TARO","5"}}).
ok
デフォルトは、detsやetsに書き込むデータは、最初の要素をキーにした2要
素のタプルになります。
この場合、'3>'だとキーはinu でデータは
{"TARO","5"}です。
キーで検索する
次は、キーを使った簡単な検索をします。
140> dets:lookup(dets1.file,neko).
[{neko,{"TAMA","4"}}]
141> dets:lookup(dets1.file,nai).
[]
データがないと空リストを返します。
member()でレコードの有無の確認
142> dets:member(dets1.file,neko). true 143> dets:member(dets1.file,nai). false
キーはfirst()とnext()で取得できます。
(テスト中にデータを追加しています。)
145> dets:first(dets1.file). rabit 146> dets:next(dets1.file,rabit). neko
最後にnext()を実行すると'$end_of_table'が返ります。
存在しないキーをnext()に渡すと、errorでは無く不定のキーが返るようです。???
147> dets:next(dets1.file,1). '$end_of_table' 148> dets:next(dets1.file,nai). rat
match()でdetsを検索する
まず、match()を使ってみます。 マニュアルはここ。
35> dets:match(dets1.file,'$1').
[[{neko,{"TAMA","4"}}],
[{inu,{"TARO","5"}}],
[{rat,{"happy","6"}}],
[{rabit,{"pyon","7"}}]]
matchの第2引数はパターンを記述しますが、'$N'はパターン変数で'$1'を指 定すると全部のレコートが返ってきます。 matchはetsでも使えます。次のmatch()では"T"で始まる内容の2番目の内容 がリストで返されています。
47> dets:match(dets1.file,{'_',{"T"++'_','$1'}}).
[["4"],["5"]]
次の例では、48>ではキーがratのデータのタプルを返し、 49>ではパターン変数で指定した位置の文字がリストで返されています。
48> dets:match(dets1.file,{rat,'$1'}).
[[{"happy","6"}]]
49> dets:match(dets1.file,{rat,{'$1','$2'}}).
[["happy","6"]]
パターン変数の番号が飛んでいる場合は、結果も飛ばされるようです。
50> dets:match(dets1.file,{'$3',{"T"++'_','$1'}}).
[["4",neko],["5",inu]]
select()でdetsを検索する。
次に、select()
35> dets:match(dets1.file,'$1'). と同じselect()は
66> dets:select(dets1.file,[{'$1',[],['$$']}]).
[[{neko,{"TAMA","4"}}],
[{inu,{"TARO","5"}}],
[{rat,{"happy","6"}}],
[{rabit,{"pyon","7"}}]]
第2引数はマッチパターンから、パターンを含むマッチスペックになります。
マニュアルより
* MatchSpec = [MatchFunction]
* MatchFunction = {MatchHead, [Guard], [Result]}
* MatchHead = "Pattern as in ets:match"
* Guard = {"Guardtest name", ...}
* Result = "Term construct"
マッチスファンクションの第3要素のリザルトを'$$'から'$_'に変えると結果 のリストのリストだったのが結果のリストになりました。
67> dets:select(dets1.file,[{'$1',[],['$_']}]).
[{neko,{"TAMA","4"}},
{inu,{"TARO","5"}},
{rat,{"happy","6"}},
{rabit,{"pyon","7"}}]
テストのためキーが数字のレコードを追加します。
102> dets:insert(dets1.file,{1,{by2,2}}).
ok
ガードのテストです。キーがatomのものを選択します。
103> dets:select(dets1.file,[{{'$1','$2'},[{'is_atom','$1'}],['$$']}]).
[[neko,{"TAMA","4"}],
[inu,{"TARO","5"}],
[rat,{"happy","6"}],
[rabit,{"pyon","7"}]]
キーが数字のものを選択します。
104> dets:select(dets1.file,[{{'$1','$2'},[{'is_number','$1'}],['$$']}]).
[[1,{by2,2}]]
キーが0以上のものを選択します。atomの場合も含むようです。
105> dets:select(dets1.file,[{{'$1','$2'},[{'>','$1',0}],['$$']}]).
[[neko,{"TAMA","4"}],
[inu,{"TARO","5"}],
[rat,{"happy","6"}],
[rabit,{"pyon","7"}],
[1,{by2,2}]]
キーが1以上のものを選択します。
106> dets:select(dets1.file,[{{'$1','$2'},[{'>','$1',1}],['$$']}]).
[[neko,{"TAMA","4"}],
[inu,{"TARO","5"}],
[rat,{"happy","6"}],
[rabit,{"pyon","7"}]]
上記のリザルトを変えてみます。['$$']から['$1']に変更しました。$1はキー がある位置なのでキーのリストが返ってきます。
107> dets:select(dets1.file,[{{'$1','$2'},[{'>','$1',1}],['$1']}]).
[neko,inu,rat,rabit]
リザルトを['$2']にすると、$2はデータのタプルの位置にあるので、タプルの
リストが返ってきます。
123> dets:select(dets1.file,[{{'$1','$2'},[{'>','$1',1}],['$2']}]).
[{"TAMA","4"},{"TARO","5"},{"happy","6"},{"pyon","7"}]
リザルトを[{{'$1','$2'}}]にしてみました。
124> dets:select(dets1.file,[{{'$1','$2'},[{'>','$1',1}],[{{'$1','$2'}}]}]).
[{neko,{"TAMA","4"}},
{inu,{"TARO","5"}},
{rat,{"happy","6"}},
{rabit,{"pyon","7"}}]
リザルトを[{{'$1',"YOKUWAKARANAI"}}]にしてみました。
135> dets:select(dets1.file,[{{'$1','$2'},[{'>','$1',1}],[{{'$1',"YOKUWAKARANAI"}}]}]).
[{neko,"YOKUWAKARANAI"},
{inu,"YOKUWAKARANAI"},
{rat,"YOKUWAKARANAI"},
{rabit,"YOKUWAKARANAI"}]
成功例だけ、ご参考まで。#エラーのたびに、open_file()が必要でした。
マッチスペックを関数から作る
selectで使用したマッチスペック(MatchSpec)ですが、これを関数から変換す
る関数(ets:fun2ms/1)があります.
上の例でマッチスペック
[{{'$1','$2'},[{'>','$1',1}],[{{'$1','$2'}}]}]
はets:fun2ms()を使って、関数を変換することで作ることができます。
1> dets:open_file(dets1.file,[]).
{ok,'dets1.file'}
2> ets:fun2ms(fun({N1,N2})when N1 > 1 -> {N1,N2} end).
[{{'$1','$2'},[{'>','$1',1}],[{{'$1','$2'}}]}]
作成したマッチスペックを使ってselectを実行しました。
3> MS=ets:fun2ms(fun({N1,N2})when N1 > 1 -> {N1,N2} end).
[{{'$1','$2'},[{'>','$1',1}],[{{'$1','$2'}}]}]
4> dets:select(dets1.file,MS).
[{neko,{"TAMA","4"}},
{inu,{"TARO","5"}},
{rat,{"happy","6"}},
{rabit,{"pyon","7"}}]
ガードで使える関数は決まっていて、http://www.erlang.org/doc/man/erlang.html で書かれているBIFs(built-in functions)の中の"Allowed in guard tests" と書かれている関数だけになります。http://d.hatena.ne.jp/n_shuyo/20070510/erlang にまとめてくれていました。
以下はis_number とis_atomを使った例です。
14> MS1 = ets:fun2ms(fun({N1,N2})when is_number(N1)-> {N1,N2} end).
[{{'$1','$2'},[{is_number,'$1'}],[{{'$1','$2'}}]}]
19> dets:select(dets1.file,MS1).
[{1,{by2,2}}]
50> MS2=ets:fun2ms(fun({N1,{N2,N3}}) when is_atom(N1) -> {N1,N2} end).
[{{'$1',{'$2','$3'}},[{is_atom,'$1'}],[{{'$1','$2'}}]}]
53> dets:select(dets1.file,MS2).
[{neko,"TAMA"},{inu,"TARO"},{rat,"happy"},{rabit,"pyon"}]
次回に続きます。

