[Glue!] サーバから受け取った情報の処理


エージェントはサーバとデータを交換しながら処理を進めていく。 ここでは perl でエージェントを作成する場合のための、 サーバから返された情報の定型的な処理法を示す。
但し、ここに挙げたコードのテストは必ずしもすべて完了しているわけではない。 Use at your own risk.

ひとつの名前に関する情報の形式

サーバから受け取った情報は GlueLogicAccess 関数によって、 ひとつひとつの名前に関する情報に分割され、それらのリストの形にまとめられて返される。 そのリストの各々の要素はひとつの文字列であり、 一般のデータのアクセスの場合、以下のような構造を持つ。
但し、これらの要素の間には何の区切り文字も挿入されない。 名前にデータが定義されていない場合は、一文字の '=' の後に文字列 'UNBOUND' が続く。

また、受け取った情報の先頭の要素として、診断メッセージが返されることがある。 診断メッセージは先頭の一文字が必ず '!' になっているので、 一般の名前に関する情報とは容易に区別することができる。

このような構造に基づいて、名前ひとつ分の情報をその部分に切り分けるためには、

( $name, $type, $val ) = ( $element =~ /^([^=]+)=(.)(.*)$/o );

( $name, $valwithtype, $val ) = ( $element =~ /^([^=]+)=(.(.*))$/o );
のようなコードを書けば良い。 サーバから受け取る名前に関する情報の順番は、 リクエストを送った時の名前の順番と同じであることが保証されているので、 特にデータの型を意識する必要の無い場合には、
$element =~ s/^[^=]+=.//o;
で、値の部分を簡単に取り出すことができる。

また、 UNBOUND が返ってきた場合の処理を必要とするなら、

( $n, $vv, $v ) = ( $element =~ /^([^=]+)=(.(.*))$/o );
$v = undef if $vv eq 'UNBOUND';
とすれば良い。


複数の名前の値の処理

一般に、 GlueLogicAccess 関数で名前のアクセスを行なう時は、 ひとつの関数で同時にできるだけ多くの名前を処理した方が効率が良い。

一度にたくさんの名前に関する情報が戻された場合には、 その戻り値が @ans にあると仮定した時、

( $val1, $val2, $val3 ) = ( map { /^[^=]+=.(.*)$/o } @ans );
で、診断メッセージの除去、ひとつひとつの名前に関する情報からの名前や型情報などの分離、 それぞれの情報の複数のスカラ変数への切り分けを、一行で同時に行なうことができる。 map の代わりに grep を使うと @ans の内容も変化するので注意。


共有資源の管理・占有方法

ここでは hoge という名称の資源 resource.hoge について、 その所有者の identifier を入れる名前として resource.hoge.owner があり、 この名前の値が $self にある自分の identifier である間は その資源を占有利用できる権限を持つものとする。

@ans = &GlueLogicAccess( "resource.hoge.owner:UNBOUND", "resource.hoge.owner=\"${self}" );

占有を開始したいときは、上記の操作を行なう。 もしも目的とする資源が既に誰か (自分を含む) に占有されているときには、 上記のトランザクションは最初の "resource.hoge.owner:UNBOUND" の処理直後に強制終了され、 @ans の最初の要素は !Condition Failed になり、 次の要素は "resource.hoge.owner?" の場合と同じ値を返す。 このため、 占有できなかった資源が誰に占有されていたかを知ることができる。

自分が資源を占有している間に、 その資源を利用する方法には特に特殊なものはないが、 心配性の利用者であれば、 資源を利用する (特に値を変更する) ときに、 その資源が自分に占有されている状態であることを確認しながら利用することになるかもしれない。

@ans = &GlueLogicAccess( "resource.hoge.owner:\"${self}", "resource.hoge.contents=\"${contents}" );

もしも、自分が占有したい複数の資源の名称がリスト @resource に用意されている場合、

@ans = &GlueLogicAccess( map{"resource.$_.owner:UNBOUND"} @resource, map{"resource.$_.owner=\"${self}"} @resource );
printf("resource %s was owned by %s\n", ($ans[$#ans] =~ /^resource\.([^.]+)\.owner=.(.*)$/o))  if  $ans[0] =~ /^!/o;

でそれらの資源を一気にすべて占有することができる。 資源のうち一つでも占有できない (既に占有されていた) 場合には、 すべての資源を占有しようともしないで終了する。 この場合は @ans の最後の要素を見ることで、 占有できなかった資源が何で、誰に占有されていたかを知ることができる。 ただし、占有できない資源はまだ他にもあったかもしれないが、それは知ることができないことに注意。

また、占有を終了したいときは、

@ans = &GlueLogicAccess( "resource.hoge.owner:\"${self}", "resource.hoge.owner=UNBOUND" );

の操作を行なう。 自分が資源を占有している状態になっていることに自信があれば、 "resource.hoge.owner=UNBOUND" だけでも構わない。


ローカル・データベースの作成

perl の連想配列を使うと、 Glue Logic Server の中にあるのと同様なデータベースをエージェント内に構築でき、 多くの情報を整然と管理したい場合に威力を発揮する。

たとえば、 GlueLogicAccess 関数からの戻り値が @ans にあると仮定した時、

foreach ( @ans ) {
    if ( ( $n, $v ) = /^([^=]+)=(.+)$/o ) { $DB{$n} = $v; }
}
で、新しく得られた値をデータ型を示す文字ごと、連想配列 %DB に代入することができる。
もしも、得られたデータの型をあまり意識しないでも良く、 かつ UNBOUND が返ってきた場合の処理を必要とするような場合には、
foreach ( @ans ) { 
    next unless ( $n, $vv, $v ) = /^([^=]+)=(.(.*))$/o;
    $DB{$n} = ( $vv eq 'UNBOUND' ) ? undef : $v;
}
とすれば良い。

蛇足ながら、このようにして作られたローカル・データベースの内容を Glue Logic に書き戻す場合には、データの型があらかじめわかっているのならば、

while ( ($n,$v) = each %DB ) { push( @ret, "$n=\$$v" ); }
&GlueLogicAccess( @ret );
とする。

特に、ある名前の構造を構成する全ての名前を調べたい場合には、 変数 $stem にその構造の名前があるとすると、

%DB = ();
$pat = quotemeta "$stem.";
foreach ( &GlueLogicAccess( "!GetDescendant $stem" ) ) {
    next unless ($n, $vv, $v) = /^$pat([^=]+)=(.(.*))$/o;
    $DB{$n} = ( $vv eq 'UNBOUND' ) ? undef : $v;
}
で良い(はず)。
この時、 $stem の内容が変化する可能性があるなら、 マッチの正規表現に o オプションを付けないように注意すること。


その他 Tips & Hacks こんな書き方もある…(備忘録兼用)

ただし readability は落ちるのであまり酔わないこと。

基本型?

( $var ) = ( (&GlueLogicAccess("Name?"))[0] =~ m/^[^=]+=.(.*)/o );

match を使えば、もとの list を壊すことはない。

同時に複数の名前の値を参照

@queries = ( "Name1?", "Name2?" );
( $var1, $var2 ) =  map{/^[^=]+=.(.*)$/o} (&GlueLogicAccess(@queries));

値が UNBOUND の場合の処理は特にしていないので、 先頭一文字を削って "NBOUND" という値を持つように見えてしまう。 言い換えれば、 "NBOUND" という文字列を持つ値と区別ができない。

一つの名前の値を参照

( $test, $var ) = (&GlueLogicAccess("Name?"))[0] =~ m/^[^=]+=(.(.*))/o;
$var = undef  if  $test eq 'UNBOUND';

値が UNBOUND の場合は、空文字列と区別するため、恣意的に undef を代入する。

一つの名前の値を参照

( $test, $var ) = map{/^[^=]+=(UNBOUND|[^U](.*))$/o} (&GlueLogicAccess("Name?"));

$test は値の型も含めた文字列、 $var が型を示す文字を取り除いた文字列。 $var の値は UNBOUND だった場合、上と同様に undef になる。
正規表現中で UNBOUND を含む選択肢が選ばれた場合には、 内側の括弧は undef になるというお約束の存在に注意。 ここでもしも $test が要らないならば、

( $var ) = map{/^[^=]+=(?:UNBOUND|[^U](.*))$/o} (&GlueLogicAccess("Name?"));

同時に複数の名前の値を hash 全体に参照

@names = ( "Name1", "Name2" );
%hash = map{m/^([^=]+)=.(.*)$/o} grep{!m/^[^=]+=U/o} (&GlueLogicAccess((map{$_.?} @names)));

UNBOUND の値を持った名前は grep で取り除かれてしまうが、 list 形式で hash に代入するなら key が無いことでそれと判るので、これで充分。
もとから hash に在った情報はすべて失われることに注意。

同時に複数の名前の値 (この例では構造を持った名前の要素) を hash の slice に参照

@hash{@names} = @ans = 
	map{m/^[^=]+=.(.*)$/o} grep{!m/^[^=]+=U/o} (&GlueLogicAccess((map{"Stem.$_?"} @names)));
die('Some of \@names are UNBOUND')  if  (@ans < @names);

UNBOUND の値を持った名前は grep で取り除かれるが、 どの値が UNBOUND だったかはこれでは判らない。 このため、 QueryVariant あたりで得た、 @names を使って参照するのが無難。
もとから hash に在った情報は失われないので安心?

同時に複数の名前の値を list に参照

( $var1, $var2 ) = map{/^[^=]+=(?:UNBOUND|[^U](.*))$/o} (&GlueLogicAccess((map{$_.'?'} @names)));

$var1 や $var2 の値は、名前の値が UNBOUND だった場合、上と同様に undef になる。

同時に複数の名前の値を hash 全体に参照

%hash = map{/^([^=]+)=(?:UNBOUND|[^U](.*))$/o} (&GlueLogicAccess((map{$_.'?'} @names)));

右辺は変数名とその値とが交互に並んだ list になる。 そのため、 hash に直接代入するのに好適。
名前の値が UNBOUND だった場合、対応する list の要素は undef になり、 hash 内では key は在るのに値は無い ( exists だが defined ではない ) という状態になる。

同時に複数の名前の値を hash の slice に参照

@hash{@names} = map{/^[^=]+=(?:UNBOUND|[^U](.*))$/o} (&GlueLogicAccess((map{$_.'?'} @names)));

UNBOUND な名前の値は undef になる。 もとから hash に在った情報は失われないので安心?

同時に複数の名前の値を hash から更新

&GlueLogicAccess((map{defined($hash{$_}) ? "$_=\"$hash{$_}" : "$_=UNBOUND"} keys %hash));

%value の key で示される名前に対応する値を代入する。 この例では全ての値は文字列型であると決めつけている。
三項演算子は exists だが defined ではない名前の値を UNBOUND にするために必要。

構造の要素の問い合わせ

@variants = split( /\s+/, (&GlueLogicAccess("!QueryVariant Stem"))[0] );

構造 (Stem) の全ての要素の値の hash 全体への参照

%hash = map{/^Stem\.([^=]+)=.(.*)$/o} (&GlueLogicAccess("!GetDescendant Stem"));

GetDescendant では UNBOUND の値は返ってこないので、 型を示す文字のチェックで手を抜ける。

AddInformTo の管理

undef %Informs;

foreach ( @names ) {
    next  if  $Informs{$_};  $Informs{$_}++;
    &GlueLogicAccess("!AddInformTo $_ $GlueLogicAgent");
}

@names の list のうち、まだ AddInformTo していないものだけ処理をする。

DelInformTo のしかた

foreach ( keys %Informs ) {
    &GlueLogicAccess("!DelInformTo $_ $GlueLogicAgent");
}

名前に使える文字の制限への合わせ方

@converted = map{s/([^A-Za-z0-9])/'_'.sprintf("%X",ord($1))/geo;$_} @names;

Glue Logic が名前として扱える文字は、 正規表現で [A-Za-z0-9_] すなわち \w だけである。 このため英数字以外の文字が現れる変数を Glue Logic の名前として扱おうとすると、 escape sequence を用いる必要がある。
ここでは [^A-Za-z0-9] を _ にその文字のコードを 16 進で表現したもので置き換えた。

escape された名前の戻し方

@names = map{s/_([A-F0-9][A-F0-9])/chr(hex($1))/geo;$_} @converted;

上記の逆変換。

識別子生成

システム内でユニークな識別子を次々に生成する。 ここでは、名前 $q に次に利用可能な識別子が XYZ0001 などといった形式で準備されているものとする。

while (1) {
    ($ident) = map {/=.(.*)/o} (&GlueLogicAccess("$q?"));
    ($new = $ident)++;
    @ans = &GlueLogicAccess("$q:\"${ident}", "$q=\"${new}");
    last if  ( $ans[0] =~ /=/o );
}

メッセージ待ち行列

メッセージ待ち行列データ構造の名前を $q とする。 この待ち行列には、 書き込み用ポインタ ${q}.in と読み出し用ポインタ ${q}.out が付属するものとする。

メッセージ送信側では、

while(1){
    ($idx) = map {/=.(.*)/o} (&GlueLogicAccess("$q.in?"));
    ($new = $idx)++;
    @ans = &GlueLogicAccess( "$q.in:\#${idx}", "$q.${idx}:UNBOUND", "$q.in=\#${new}", "$q.${idx}=\"${msg}" );
    last if  ( $ans[0] =~ /=/o );
}

メッセージ受信側では、

undef $msg;
while(1){
    ($idx, $idxin) = map {/=.(.*)/o} (&GlueLogicAccess("$q.out?", "$q.in?"));
    last  if  $idx == $idxin;
    ($new = $idx)++;
    @ans = &GlueLogicAccess( "$q.out:\#${idx}", "$q.out=\#${new}", "$q.${idx}?" );
    last if  ( $ans[0] =~ /=/o );
}
&GlueLogicAccess( "$q.${idx}=UNBOUND", "$q.in:\#${new}", "$q.out:\#${new}", "$q.in=\#0", "$q.out=\#0" );
($msg) = ( $ans[$#ans] =~ /=.(.*)/o );


 [M.T. HomePage]  [written & copyrighted by Masayuki Takata]