本文へジャンプ

ソフトウェア > Lotus > Lotus Developer Domain > 

Iris Today Archives

Rnext の式言語の強化


Lotus Software
by Michelle Mahoney and Robert Perron
レベル:上級者
対象:Domino Rnext
原文の掲載:2000年7月12日

Iris Today Archivesの原文(US)

インデックス
以前の Rnext
Rnext の @For ループ
@While ループ
@Max と @Min の強化
@Compare
@Sort
@Transform と @Nothing
@Count
@IfError
@GetValue、@ThisName、@ThisValue

ご注意: この記事は、ノーツ Rnext ベータ・プログラムで計画・開発されている機能を紹介します。したがって、最終的な機能や UI と、記事中に紹介されるものは異なる可能性があります。

R4 のロータス スクリプトから始まったノーツ/ドミノへのプログラミング言語の導入によって、式言語の衰退が予測されました。しかしながらソース・コードがコンパクトであるということと、その優れたパフォーマンスから、今でも好まれ続けているツールです。式言語に足りないものといえば、特にループのような、プログラミング言語にある制御機能でした。

ノーツ/ドミノ Rnext は式言語を徹底的に見直し、多くの新しい@関数を導入します。この記事は、式言語の制御機能へ加えられる次の機能拡張に重点を置いています。
  • @Set を使わずとも一時的変数を再利用することができる。
  • リストを添字付きすることができる。
  • 割り当てステートメントを入れ子にすることができる。
  • 大括弧を使って引用と定数の範囲を定めることができる。
  • @For、@While、@DoWhile とループを同時に使うことができる。
  • リスト処理の機能拡張には @Comare、@Transform、@Nothing、@Count、@Sort、@Max(パラメーター1つ)、そして @Min(パラメーター1つ)があります。
  • @GetField、@ThisName、そして @ThisValue によってフィールドの名前をハード・コードすることなく、フィールドにアクセスできるようになります。
  • @IfError はエラー処理を強化します。


以前の Rnext
以前の Rnext の式言語には、リストの処理をするために2つの強力な、しかし制限された技術がありました。
  • オペレーターと@関数は、リスト内のすべての要素に対して同じ操作を実行することができます。例えば1つのステートメントでは、2つのリストの相応する要素を追加することができるか、または1つのリストの要素を合計することができます。
  • エージェントは複数の文書に対して1つの式を実行できます。例えば、1つのステートメントで、ビュー内にあるすべての文書にフィールドを2つずつ追加することができます。
これらの能力は、最小限のコーディングで多くの仕事をこなせるようにしてくれます。これらはベクターを処理するマシンのベクター命令とよく似ています。

しかしながらこれらの技術は、ベクター命令と同じように、リスト内の他の操作に依存している操作を実行できないという、深刻な不足部分もあります。例えば、2つのリストに依存している要素を実行していて、それぞれの操作はリストのすぐ前の要素の結果を必要とするとしましょう。あるいは @Prompt を使って、それぞれのリスト要素の入力を得たいとしましょう。これらのケースでは、操作を連続化しなければなりません。ここがリスト処理の弱い部分です。

次のコードは簡単なリスト操作を表しています。これは、フォーム上のボタンで使われることがあるでしょう。

REM {This single statement assigns all the elements of one list to another.};
FIELD ListResult := ListOne

もう1つ挙げておきます。

REM {This statement adds the corresponding elements in two lists.};
FIELD ListResult := ListOne + ListTwo

そして、次は圧縮(reduction)操作です。

REM {This statement sums the elements in one list.};
FIELD Result := @Sum(ListOne)

これらのリスト操作は、以前の Rnext と Rnext 式の中で使えます。

メモ: 例の中で引用の範囲を定めるために、大括弧が使われていることに注目してください。これは Rnext で新しく導入されたもので、特に引用符を含んだ引用の範囲を定めるために便利です。Rnext での大括弧の使用方法については、大括弧についてを参照してください。

これらの問題は、リスト操作として表すことができます。次の図にあるように、例えば、2つ目の問題で、各構成要素操作(each constituent operation)は独立しているのです。

List operation example

3番目の問題では、構成要素操作は何に対しても依存していません。これらの構成要素は、どんな順番でも合計することができます。また例えば、これらの構成要素をグループ化して合計し、その後これらのグループを合計する、といったこともできます。

List operation sum example

しかしながら、私たちがまだ解決策を提供できていない、もう1つの問題があります。各構成要素操作は、1つ前の構成要素操作の結果に依存しています。リスト要素は順々に処理しなければならないのです。

Sequential list operation example
 
上に戻る
 
Rnext の @For ループ
Rnext でのループの使い方を理解するために、ListOne を ListResult に割り当てる簡単なループから始めてみましょう。これは ListResult := ListOne と同じです。実際にはリスト操作を使いますが、この練習問題はループを分析し、新しい関数を紹介するためだけのものです。

REM {Assign ListOne to ListResult using an @For loop.
Use concatenation on all but the first iteration.};
   @For(n := 1; n <= @Elements(ListOne); n := n + 1;
   FIELD ListResult := @If(
     n = 1;
     ListOne[n];
     ListResult : (ListOne[n])
   )
)

この短いコードには、多くの新しい関数が含まれています。
  • 一時的な変数である n は繰り返し割り当てられています!以前の Rnext では一時的変数を @Set でしか繰り返し割り当てることができなかったので、変数を利用する操作は式の中ではできませんでした。
  • ListOne が添字付きになっています!これは添字整数を含んだリスト内の要素と同じようなことで、1は1番目の要素を表しています。以前の Rnext 式では、個々のリスト要素での作業は非常に難しいことでした。@Implode と @Explore はいくつかの初歩的な能力を提供していましたが、リストの中をループする能力は含まれていませんでした。@Elements を使ってリストの容量を測定してください。
  • このループは新しい @For を使います。予想通り、この @For はプログラミング言語で使われる For ループと似ています。1つ目のパラメーターはループ変数に反復を始める場所を割り当て、2つ目のパラメーターは各反復の条件を指定し、3つ目のパラメーターはループ変数を増殖させ、残りのパラメーターはループのボディーを構成します。
添字を使う際の制限の1つは、割り当て部分の左手に表示させてはいけないことです。したがって例えば上のコードでは、このようにしてはいけません。

ListResult[n] := ListOne[n]

ListOne 要素を連結しながら ListResult を作成しなければならないのです。

いつものように、連結される式のまわりには必ず丸括弧を付けてください。連結オペレーターが高い優先権を持っているのがその理由です。

引き続き簡単なループ
次のループは、2つ目のリスト処理の例(ListOne+ListTwo)を複製します。

REM {This code adds 2 lists using a loop.};
@For(n := 1; n <= @Elements(ListOne); n := n + 1;
   FIELD ListResult := @If(
     n = 1;
     ListOne[n] + ListTwo[n];
     ListResult : (ListOne[n] + ListTwo[n])
   )
)

次のループは、要素を1つのリスト(ListOne の @Sum)にまとめます。

REM {This statement sums the elements in one list using a loop.};
FIELD Result := 0;
@For(n :=1; n <= @Elements(ListOne); n := n + 1;
   FIELD Result := Result + ListOne[n]
)

問題ループ
次は、リスト操作では解決できない問題を見てみましょう。2つのリストに入っている要素の各セットは合計し、この合計をさらに1つ前のリスト要素と合計させたいとしましょう。(この問題は、その時点での要素をすべて合計する、という見方もできます)それぞれの構成要素操作は、1つ前の構成要素操作の結果を必要とするのです。

REM {We cannot do this with a list operation because each add
uses the result of the previous add.};
@For(n := 1; n <= @Elements(ListOne); n := n + 1;
   FIELD ListResult := @If(
     n = 1;
     ListOne[n] + ListTwo[n];
     ListResult : (ListResult[n-1] + ListOne[n] + ListTwo[n])
   )
)
 
上に戻る
 
@While ループ
新しく導入された @While を使ったコードが下にあります。必要以上に長く書かれているのは、何が起きているかを簡単に見極められるようにしてあるためです。@While はループの最初にある1番目のパラメーターが指定する環境をチェックします。環境が正しく設定されていれば、ループの本文 (残りのパラメーター) が実行されます。その後、環境にループバックします。

REM {We cannot do this with one operation because the @Prompt statements must be serial. This loop collects string items one item at a time by prompting the user and adds them to a list. };
item := @Prompt([OKCANCELEDIT]; "Item"; "Enter item or \" \""; "");
@While(
   item != "";
   FIELD ListResult := @If(
     ListResult = "";
     item;
     ListResult : item
   );
   item := @Prompt([OKCANCELEDIT]; "Item"; "Enter item or \" \" to quit";     "")
)

この例では、 @Prompt を使ってユーザー入力を取得しています。ユーザー入力が空のストリングでない場合に、@While はループの本文を実行するのです。(ユーザーは @Prompt の [キャンセル] を押すことで終了することも可能です。) するとコードは、これらのアイテムを ListResult に連結させるか、ListResult が空の場合にはこれに割り当てます。そして最後にコードが再度プロンプトされ、テストのためにループ・バックします。

次に書かれているコードは、上に書かれているコードの短縮版です。"item" をループの前に割り当てるか、ループ本文の最後のステートメントに割り当てる代わりに、@While の条件ステートメントとして1度だけ割り当てています。割り当てステートメントが入れ子になっていることに気付いてください。

REM {This is a condensed version of the @While loop. };
@While(
   (item := @Prompt([OKCANCELEDIT]; "Item"; "Enter item or \" \""; "")) !=     "";
   FIELD ListResult := @If(
     ListResult = "";
     item;
     ListResult : item
   )
)

次のコードは @DoWhile ループです。リスト内の要素を最初の要素から最後の要素、もしくはその内容が END*OF*LIST の要素まで1つずつ表示させます。条件ステートメントは最後にきます。

REM {An @DoWhile loop processes the loop body
before checking the condition.};
@If(ListOne = ""; @Return(0); "");
n := 0;
@DoWhile(
   n := n + 1;
   @Prompt([OK]; "Element " + @Text(n); ListOne[n]);
   ((n < @Elements(ListOne)) & (ListOne[n] != "END*OF*LIST"))
)
 
上に戻る
 
@Max と @Min の強化
以前まで @Max と @Min は、2つの番号の最大値と最小値の特定、もしくは2つの番号リストをペアにし比較することから得られる最大番号と最小番号の番号リストを作成するために使われていました。Rnext ではさらに @Max と @Min に1つ番号リストをパスし、そのリストの最大番号と最小番号を特定することも可能となりました。例えば R5 では、第1四半期の合計売り上げで、どの営業マンが最も良い成績を残したかを、@Max を使って特定することができます。

maria_sales := 150000:80000:50000;
anthony_sales := 25000:170000:20000;
best := @Text(@Max(@Sum(maria_sales);@Sum(anthony_sales)));
@If(best= @Text(@Sum(maria_sales));"Maria with " + best;"Anthony with " + best)

この式の結果は "Maria with 280000" です。またその四半期で、3つの最も悪かった月別売り上げ合計をリストすることも可能です。

@Min(maria_sales;anthony_sales)

この式の結果は "25,000; 80,000; 20,000" です。Rnext では @Max を使って最も営業成績が良かった月を特定することもできます。

maria := @Max(maria_sales);
anthony := @Max(anthony_sales);
@Max(maria;anthony)

この式の結果は "170,000" で、アンソニーの四半期半ばの営業成績を表しています。1つの番号リストから最大値と最小値を抽出する能力は、@Max と @Min にとって強力な機能拡張となりました。
 
上に戻る
 
@Compare
既存の関数が強化されリストの操作性を向上させただけでなく、Rnext では新しく関数を追加してリストの操作性をさらに向上させています。@Compare はその1つです。特にテキストリストを扱う場合において、柔軟性を加えるものです。@Compare は、2つ目のリスト内の要素が、1つ目のリスト内の要素より多いか少ないかを比べるという意味では、@Max や @Min に似ています。違いは何かというと、@Compare は番号や番号リストでなく、テキストやテキスト・リストを比較するのです。アルファベットの順番をベースに、テキスト・リストから文字の値を引き出すのです。例えば、@Compare を使うことで custName というフィールドのテキスト・リストから、a、b、そして c から始まる単語を抽出するといったことができるようになります。

@For(n := 1; n <= @Elements(custName); n := n+1;
FIELD result := @If(n = 1;@If(@Compare(custName[n];"d"; [CaseInsensitive]) = -1;custName[n];"");@If(@Compare(custName[n];"d"; [CaseInsensitive]) = -1;result:(custName[n]);result)));
result
 
上に戻る
 
@Sort
@Sort はリストを取り扱う場合において柔軟性を加えたもう1つの新しい関数です。@Sort はテキスト、番号、日付時刻と、どんな種類のリストにも使えます。@Sort を使うことでリストに表示させる要素の順番を決めることができます。例えば、値段の列 (productView ビューの4番目の列) から、特定の商品グループの値段を取得し、降順で表示させることができます。

@Sort(@DBLookup("";"Server/Name/Notes":"Directory\\DbName.nsf";
"productView";"ProductFamilyKeyword";4);[Descending])

あるいは、テキスト・リストをソートし、アルファベットの逆順で表示させることも可能です。例えば、movies フィールドに "titanic"、"Jaws"、"Wizard of Oz" というリストが含まれている場合には、このような式を使います。

@Sort(movies;[Descending]:[CaseInsensitive])

式は "Wizard of Oz; titanic; Jaws"を返してきます。@Sort はさらに、[CustomSort] と呼ばれる強力なキーワード・オプションを持っています。これは、$A と $B という2つの変数を予約し、リスト内にある要素を1度に2つずつ比較する、などの時に使えます。例えば @Sort を使って映画のリストを、タイトルの短い順に表示させるということもできるのです。

@Sort(movies;[CaseSensitive]:[CustomSort];@If(@Length($A) < @Length($B);-1;@Length($A) > @Length($B);1;0))

式は "Jaws;titanic;Wizard of Oz" と返してきます。

CustomSort キーワードが、CaseSensitive キーワードより優先していることに注意してください。
 
上に戻る
 
@Transform と @Nothing
リスト操作への追加の機能拡張は、@Transform 関数によって供給されます。@Transform を使うことでリストの各要素に式を適用し、要素ごとの結果を表示するリストを返すことができます。例えば、@Transform を使って、ユーザーがいかなる形でフィールドに名前を入力しようとも、必ず姓、名の順でフォーマットさせることができるのです。Names フィールドに次のテキスト・リストが含まれているとしましょう。

"James Brown" : "White, Fred" : "Smith, John" : "Doug Smith"

次の式を使うと Names フィールドのリストは : "James Brown" : "Fred White" : "John Smith" : "Doug Smith" へと変換されます。

@Transform( Names; "x";
   @If(@Contains( x; ",");
     @Right( x; ",") + " " + @Left( x ; ",");
     x))

次のリストを返します。

"James Brown" : "Fred White" : "John Smith" : "Doug Smith"

途中の段階で結果を返したくない場合は @Nothing を使います。例えば、1つ前の式の結果がフィールドに含まれている時に、"J" から始まる名前だけを取得したいのであれば、次のようにします。

@Transform( Names; "x";
   @If( @Begins( x; "J");
     x;
     @Nothing));

すると次の結果が返ってきます。

"James Brown" : "John Smith"
 
上に戻る
 
@Count
@Count は、テキスト、番号、日付時間のリストから要素を取得する際に、エラーが起こる可能性を最小限に抑えてくれます。@Count は @Elements 関数とほぼ同じですが、@Count はリストとして供給された値が null かまたはリスト・データ・タイプを持っていない場合に、0ではなく1を返します。このため、要素カウントが複雑なタスクをこなすために他の関数と連結して使用されるときに、エラーになりにくくなるのです。

例えば、6つのエントリーを含むビューから3番目のエントリーを返したい時は、次のようなコードが利用できます。

names := @DBLookup("";"Server/Name/Notes" : "names.nsf" ; "Groups"; "MyCoworkers" ; "Members");
@Subset(@Subset(names;@Elements(names)/2));-1)

しかしながら、参照に失敗し名前が返されない場合は "The second argument to @Subset must not be zero" というエラーが表示されます。このエラーを未然に防ぐためには上の式の @Elements を @Count に変更します。するとこの関数は、引き出されたリストの1番目のエントリーを返します。
 
上に戻る
 
@IfError
@IfError はエラー処理を強化するための新しい関数です。それは、ノーツによって生成されるエラー・メッセージを自由にカスタマイズできるようになるからです。

例えば、もしあなたがマリアとアンソニーの1年間の毎月の販売合計を比べたいが、アンソニーが第四半期に休暇を取ったことを知っているとしましょう。そこで、2つのリストを比較する時 (デフォルトではこうなります) 彼の9月の販売合計を3回も繰り返したくない場合は、次の式を使います。

tanthony := @Elements(anthony_yrtotal);
tmaria := @Elements(maria_yrtotal);
dif := (tanthony - tmaria);
result := @If(@Sign(dif) = -1;@Subset(maria_yrtotal; tmaria-@Abs(dif));
@Subset(anthony_yrtotal;(tanthony - dif)));
@If(@Sign(dif)=-1;@Max(anthony_yrtotal;result);
@Max(result;maria_yrtotal))

この式は、1年間の内のはじめの9ヶ月分における、最も高い月当り売上高を表示させます。言い換えると、二人のセールス・パーソンが結果を出した月だけの結果を表示させるということです。

アンソニーの売り上げ成績をまったくフォームに追加せず anthony_yrtotal フィールドを null のままにしておくと、この式は2つのエラーを発生させます。両方のエラーが @Subset 関数によって引き起こされるのです。@Subset 関数はその1番目のパラメーターにリストを必要する上に、2番目のパラメーターとして0を受け付けないのです。

1つ目のエラーを防ぐためには、式の最後を次のように書き直し、anthony_yrtotal フィールドが空であることをユーザーに通知しなければなりません。

@IfError(@If(@Sign(dif)=-1;@Max(anthony_yrtotal;result);
@Max(result;maria_yrtotal));"One of your list fields is empty");

意味が分かり難いエラー・メッセージを表示する代わりに、@IfError 関数は目的のフィールドに "One of your list fields is empty" というテキストを表示させます。これにより、ユーザーにデータの入力を促すのです。

2つ目のエラー、"The second argument to @Subset must not be zero" を防ぐためには、tantony と tmaria 割り当てステートメント内の @Elements 関数を @Count に置き換えます。@Count は、評価中のフィールドが null である場合に、0ではなく1を返すのです。
 
上に戻る
 
@GetValue、@ThisName、@ThisValue
@ThisValue と @ThisName によって、式の中へフィールド名をハード・コードしなくとも、現在のフィールドを参照する式を書くことができるようになります。

1つのアプリケーションの複数のフィールドでコードを繰り返し使うことは、カット&ペーストと同じくらい簡単なことであり、フィールド名を訂正するために編集作業をする必要はありません。
フィールド名が変わることはしばしばあり得えますが、手作業でコードを訂正する必要がなくなります。

@ThisValue と @ThisName は特に、再利用可能な変換式と確認式を書く場合に便利です。例えば、次の入力確認式は、現在のリスト・フィールドからユーザーが2つ以上選択することを求める式です。通常では、式の中にフィールド名をハード・コードしなければならないのですが、@ThisValue を使えばこのように書くことができるのです。

@If((@ThisValue != "") & (@Elements(@ThisValue) = 1);@Failure("The " + @ThisName + "field must contain more than one choice");@Success)

同じ確認作業を必要とするアプリケーションの他のフィールドにも、編集することなくこのコードをコピー&ペーストできます。次の入力変換式は、メール memo (テンプレート)の CopyTo フィールドに登録くされている受信者のために、メール・アドレスから重複しているドメインを取り除くものです。

@OptimizeMailAddress(@ThisValue)

これと同じコードが、そのままの状態で SendTo と BCC フィールドにも利用できるのです。

@GetField は、さらに多くの機能を提供します。@ThisName と一緒に使うと、何度も再利用できる一般のフィールド式を、より簡単に作成できるようになります。例えば、マリアとアンソニーがドーナツの小売業を担当した場合、次の表のように売り上げデータを保存したとしましょう。

ChocolateGlazed_
quantity
ChocolateGlazed_
price
ChocolateGlazed_
total
HoneyDipped_quantity HoneyDipped_price HoneyDipped_total
Powdered_quantity Powdered_price Powdered_total


R5 では、ChocolateGlazed_total フィールドは、次のコードに似たコードによって構成されます。

ChocolateGlazed_quantity * ChocolateGlazed_price

同じコードを各 total フィールドにも使うでしょうが、ドーナツの種類によっていちいち編集する必要がありました。Rnext では、ChocolateGlazed_field には次のコードが使えます。

doughnutName := @Left(@ThisName;"_");
(@GetField(doughnutName + "_quantity"))*(@GetField(doughnutName + "_price"))

このコードであれば、フィールド名をコード内に書き込む必要がないので、すべてのドーナツの total フィールドに同じコードを利用できるのです。



著者について
ミシェル・マホニーは、ノーツ/ドミノユーザー・アシスタント・グループの執筆者で、現在は Rnext の式言語関連のドキュメンテーションの更新作業をしています。

ロバート・ペロンは、ウェストフォード、マサチューセッツにある、ロータスのドキュメンテーション・アーキテクトです。6年以上にわたって彼はノーツとドミノのドキュメンテーションを開発し、特にプログラマビリティに集中してきました。彼はロータス スクリプトと Java ノーツ・クラスのドキュメンテーションを開発し、60 Minute Guide to LotusScript 3 - Programming for Notes 4 という本の著者の1人でもあります。「R5.0.2b の COM からのドミノ・オブジェクトへのアクセス」という Iris Today の記事は彼が執筆したものです。


 
上に戻る