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"}]
次回に続きます。