Lisp M式への回帰(スクリプト言語GavaOne事例集<体験版>)


GavaOne言語とは、「Lisp言語の良いところ」に「M式様リスト」を取り入れ、 それらを「手続き型言語から拝借した糖衣表現(syntax suger)」で包み込んでできたスクリプト言語です。
「M式様リスト」とは、Lisp S式の代りに中間コードまたはデータ構造に採用した、 M式に似た(M式様の)コンピュータ内部データ構造を言います。
Lisp言語の能力(リスト処理)に憧れを持ちながら、手続き型言語に慣れ親しんできた (”Lisp S式は、ちょっと”と敬遠している)方のご期待に応えることができると思います。
なお、本ソフトは、OS Windows10の環境下で、java言語を使用して開発・稼働します。

(目次)

===> Top Page

1. はじめに

 GavaOne言語は、ビジネス・シミュレーション用スクリプト言語として、筆者が開発している言語で、 その「体験版」による処理事例を、ご紹介いたします。
 次のページを参照しながら見ていただければ、より一層のご理解が進むものと思います。
  Lisp M式への回帰(スクリプト言語GavaOne概説書<体験版>)
 なお、この事例集の内容は、予告なしに変更することがあります。

2. 稼働環境の整備

OS Windows10の環境下(32bit、メインメモリー1Gバイト以上)、次の操作で稼働可能となります。


  1)「java言語の運用環境 jdk1.8.0_121」のない方は、ORACLE社のサイトから、ダウンロードできます。
  2) 新しくワーク用フォルダーをとり、「GavaOneDemo.jar」をダウンロード・フォルダーから、そこに移動します。
  3) パソコンを起動して、「コマンドプロンプト」を表示します。
  4) 「GavaOneDemo.jar」のある新しいワーク用フォルダーに、コマンドのコントロールを移動します。
  5)「java -jar GavaOneDemo.jar」と入力し、Enterキーを押下します。
  6)「1:> 」を表示すれば、GavaOneの関数を受け付ける準備が整いました。
  7) 「1:> 」に続けて、「add[1,2];」と入力して、次の行に、結果3を表示すれば、正常に立ちあがりました。
      以下、「n:> 」に続けて関数を入力し、結果をお楽しみください。(「n:> 」のnは、入力ラインナンバーを表します)
  8)  処理を終了するときは、「n:> 」に続けて、`end(バッククオート+end)を入力するか、
      または、何も入力せず、Enterキーを2度以上押下します。
なお、GavaOne言語体験版ソフトウエア(GavaOneDemo.jar)を入手していない方は、   こちらから入手(ダウンロード)できます。
 
 (お知らせ)
 2017年3月31日 OS Windows10の稼働環境に移行しました。
 2017年3月31日以前にダウンロードされた方は、その後のGavaOne言語の改造(含、バグフィックス)が
  反映された最新のソフトウエアを、ダウンロードされるようお勧めいたします。
 2016年8月14日、本ソフトウエアの応用目標の一つであるメンタルモデルの概要を作成いたしました。
  このメンタルモデルに、ご興味のある方は、 心の哲学仮説(トシーマの哲学ノート)をご覧ください。
 

3. 四則演算処理例

 四則演算処理関数の処理例を掲げます。 リストの領域まで、処理機能を拡張しています。


 1)足し算(add)
   > add[1,2,3,4,5];     (1+2+3+4+5)を計算して、15を返す。
     15
   > add[(1,2,3,4),5];   ((1+5),(2+5),(3+5),(4+5))を計算して、リスト(6,7,8,9)を返す。
     (6,7,8,9)
    > add[(1,2),(3,4)];   ((1+3),(2+4))を計算して、リスト(4,6)を返す。
     (4,6)
 2)引き算(subtract)  > sub[10,1,2,3]; (10-1-2-3)を計算して、4を返す。 4  > sub[(6,7,8),3]; ((6-3),(7-3),(8-3))を計算して、リスト(3,4,5)を返す。 (3,4,5) > sub[(9,7),(3,4)]; ((9-3),(7-4))を計算して、リスト(6,3)を返す。 (6,3)
 3)掛け算(multiply)   > mult[1,2,3,4,5]; (1*2*3*4*5)を計算して、120を返す。 120   > mult[(1,2,3,4),5]; ((1*5),(2*5),(3*5),(4*5))を計算して、リスト(5,10,15,20)を返す。 (5,10,15,20) > mult[(1,2),(3,4)]; ((1*3),(2*4))を計算して、リスト(3,8)を返す。 (3,8)
 4)割り算(divide)  > div[12,4,2]; (12÷4÷2)を計算して、1.5を返す。 1.5  > div[(12,4),2]; ((12÷2),(4÷2))を計算して、リスト(6,2)を返す。 (6,2)   > div[(12,5),(4,2)]; ((12÷4),(5÷2))を計算して、リスト(3.0,2.5)を返す。 (3.0,2.5)

4. リストの登録と参照

 リストをフレーム・テーブルに登録し、編集する。


    > x:=(1,2,3);         大域変数(以下、「変数」と言う) x は、リスト(1,2,3)である旨、フレーム・テーブルに登録する。
     'x
    > y:=(4,5,6);         変数 y は、リスト(4,5,6)である旨、フレーム・テーブルに登録する。
     'y
    > head[x];            x の先頭の要素1を取り出す。
     1
    > tail[x];            x の2番目以降の要素2,3を取り出し、リスト(2,3)を返す。
     (2,3)
    > append[head[x],tail[x]]; tail[x]の結果(2,3)の先頭に、head[x]の結果1を挿入すれば、
     (1,2,3)                   元のリスト(1,2,3)を得る。
    > z:=list[*x,*y];     変数 z を、変数x,yのインスタンス変数値を要素に持つリストとする。
     'z
    > *z;                 変数 z を静的評価すれば、変数zのインスタンス変数値リスト((1,2,3),(4,5,6))を返す。
     ((1,2,3),(4,5,6))
    > adjust[z];          変数 z のインスタンス変数値をベクトル化すれば、リスト(1,2,3,4,5,6)を得る。
     (1,2,3,4,5,6)
    > ppf[('x,'y,'z)];    フレーム名'x,'y,'zのフレーム定義関数(body)をプリントすれば、その一覧表を返す。
     x:=(1,2,3);
     y:=(4,5,6);
     z:=list[*x,*y];
     0
    > ppv[('x,'y,'z)];    フレーム名'x,'y,'zのインスタンス変数値(value)をプリントすれば、その一覧表を返す。
     x::(1,2,3);
     y::(4,5,6);
     z::((1,2,3),(4,5,6));
     0

5. リストの編集と高階関数

 リストを編集し、高階関数でリスト要素の集計などを行う。


    > a1:=iota[8];       変数 a1 を生成するように、フレーム'a1を定義・登録する。
     'a1
    > *a1;               フレーム'a1を静的評価する。'a1のインスタンス変数値(1,2,3,4,5,6,7,8)を返す。
     (1,2,3,4,5,6,7,8)
    > b1:=array[a1,(3,3),0]; 変数a1をもとに、3行3列のリスト(行列)b1を生成するように、フレーム'b1を定義・登録する。
     'b1
    > *b1;               フレーム'b1を静的評価する。'b1のインスタンス変数値((1,2,3),(4,5,6),(7,8,0))を返す。
     ((1,2,3),(4,5,6),(7,8,0))
    > sum[b1];           変数b1の値(行列)の数値要素を合計する。 結果36を返す。
     36
    > apply['add,b1];    変数b1の値(行列)の列方向の集計値(縦計)を求める。 結果(12,15,9)を返す。
     (12,15,9)           apply['add,b1];は、中置型表記では、'add/b1;と書ける(推奨)。
    > map['add,b1];      変数b1の値(行列)の行方向の集計値(横計)を求める。 結果(6,15,15)を返す。
     (6,15,15)           map['add,b1];は、中置型表記では、'add//b1;と書ける(推奨)。
    > c1:=refer[2,b1];   変数b1の先頭から2要素を取り出して、リスト((1,2,3),(4,5,6))をつくるフレームc1を定義・登録する。
     'c1
    > cross['add,*c1];   ((1,2,3),(4,5,6))のサブリスト(1,2,3)とサブリスト(4,5,6)の各要素を組み合わせて、
     ((5,6,7),(6,7,8),(7,8,9))  それぞれを加算した数値を要素にもつリストをつくる。
                                cross['add,*c1];は、中置型表記では、'add/^*c1;と書ける(推奨)。

6. 行列計算

 リスト処理が得意とする「行列計算」の例を、以下に示します。(フレーム定義の返り値は省略しています)


 1)行列の加算
    > a:=((1,2),(3,4));            行列aを定義する。
    > b:=((5,6),(7,8));            行列bを定義する。
    > 'add//pair[a,b];             行列a,bの加算結果のリスト((6,8),(10,12))
     ((6,8),(10,12))               を表示する。

  2)行列の減算
    > a:=((1,2),(3,4));            行列aを定義する。
    > b:=((2,1),(5,6));            行列bを定義する。
    > 'sub//pair[a,b];             行列aから行列bを減算した結果のリスト((-1,1),(-2,-2))を表示する。
     ((-1,1),(-2,-2))

  3)行列の行と列を入れ替える(転置行列の作成)
    > a:=((1,2,3),(4,5,0));
    > transpos[a];                 リスト((1,4),(2,5),(3,0))を表示する。
     ((1,4),(2,5),(3,0))

  4)行列を列方向に合計する
    > a:=((1,2,3),(4,5,6));
    > 'add//a;                     リスト(6,15)を表示する。 
     (6,15)

  5)行列を行方向に合計する
    > a:=((1,2,3),(4,5,6));
    > 'add/a;                      リスト(5,7,9)を表示する。 
     (5,7,9)

  6)行列の掛算をする
    > a:=((1,2),(3,4));            行列aを定義する。
    > b:=((2,1),(5,6));            行列bを定義する。
    > c:=transpos[b];              行列bの転置行列を求める式を定義する。
    > d[?h!]:='add/'mult/h!;       行列要素の積和計算ルーチンを定義する(h!は、局所変数)。
    > cross['d,(a,*c)];            行列a,bの掛算の結果リスト((12,13),(26,27))を表示する。
     ((12,13),(26,27))

  7)単位行列をつくる
    > a:=iota[3];                  リスト(1,2,3)を生成するフレーム文'aを定義する。
    > cross['eq,(*a,a)];           リスト((1,0,0),(0,1,0),(0,0,1))を表示する。
     ((1,0,0),(0,1,0),(0,0,1))

  8)対角行列をつくる
    > a:=(4,5,6);                  行ベクトルを定義する。
    > b:=iota[size[a]];
    > c:=cross['eq,(*b,b)];        単位行列cをつくる。
    > 'mult//pair[a,*c];           対角行列((4,0,0),(0,5,0),(0,0,6))を表示する。
     ((4,0,0),(0,5,0),(0,0,6))

  9)行列に列ベクトルを掛け算する(連立方程式を解く)
     ;  次の鶴亀算を解き、解のベクトル (x,y)を求める。
      ;      x +  y =  7      鶴と亀の数をあわせれば、7匹です。
      ;     2x + 4y = 20      鶴の足と亀の足をあわせれば、20本です。 
    ;                           鶴と亀の数は、それぞれ、何匹ですか?
    > a:=((1,1),(2,4));            連立方程式の左辺の係数を、行列aと定義する。
    > b:=inverse[a];               aの逆行列bを得る式を定義する。
    > c:=(7,20);                   連立方程式の右辺の係数を、列ベクトルcと定義する。
    > 'add//'mult//'distr/(*b,c);  逆行列bに、右から列ベクトルcを掛けて、解ベクトル(4.0,3.0)を表示する。(鶴4匹、亀3匹)
     (4.0,3.0)

 10)逆行列を計算する(行列((1,1),(2,4))の逆行列を求める)
    > a:=((1,1),(2,4));            行列を定義する。
    > inverse[a];                  逆行列((2.0,-0.5),(-1.0,0.5))を表示する。
     ((2.0,-0.5),(-1.0,0.5))

 11)逆行列計算の結果を検証する(行列とその逆行列を掛ければ、単位行列になる)
    > a:=((1,1),(2,4));            行列aを定義する。
    > b:=inverse[a];               aの逆行列bを得る式を定義する。
    > c:=transpos[a];              行列aの転置行列を求める式を定義する。
    > d[?h!]:='add/'mult/h!;       行列要素の積和計算ルーチンを定義する(h!は、局所変数)。
    > cross['d,(*b,*c)];           行列b,cの掛算の結果リスト((1.0,0.0),(0.0,1.0))を表示する。
     ((1.0,0.0),(0.0,1.0))

7. パターン・マッチングとマスク変数

 パターン・マッチング処理の事例を、以下に示します。(フレーム定義の返り値は省略しています)
 前置型関数表現?または??ではじまるマスク変数の次の性質に留意しながら、以下の事例を参照してください。


 1)リストの単純マッチング(前置型関数 ?と??の働きの違いを理解します)
    > (1,?x!,3)==(1,2,?y!);   リスト(1,?x!,3)と(1,2,?y!)をパターン・マッチングする。
     3                        結果3(マッチ)を返す。
    > (x!,y!);                変数x!,y!の値をリスト形式で参照する。
     (2,3)                    変数x!,y!の値は、それぞれ2と3である。
    > (?x!,??z!)==(1,2,3);    リスト(?x!,??z!)と(1,2,3)をパターン・マッチングする。
     2                        結果2(マッチ)を返す。
    > (x!,z!);                変数x!,z!の値をリスト形式で参照する。
     (1,(2,3))                変数x!,z!の値は、それぞれ1と(2,3)である。
    > (??z!)==(1,2,3);        リスト(??z!)と(1,2,3)をパターン・マッチングする。
     1                        結果1(マッチ)を返す。
    > z!;                     変数z!の値を参照する(変数z!に複数要素のリスト(1,2,3)が代入されたことに留意する)。
     (1,2,3)                  変数z!の値は、(1,2,3)である。

 2)連想リストの検索(パターン・マッチング時には、マスク変数のインスタンス変数値に、比較相手項目の値が与えられる)。
    > tanka:=(('apple,80),('banana,50),('orange,30),('peach,200)); 連想リスト(単価表)を定義する。
    > assoc[('orange,?x!),tanka]; 'orangeの単価を調べます。マスク変数?x!に、'orangeの単価が入る。
     3                             リストtankaの左から3番目の要素にマッチした。
    > x!;                          'orangeの単価を表示する。
     30

 3)連想リスト中の複数サブ・リストの抽出(マスク変数の確信度によって、マッチ、アンマッチが決まることを理解します)
    > tanka:=(('apple,80),('banana,50),('orange,30),('peach,200));
    > xx:=nil|assoc[`sv,$xx];  フレーム文'xxに、確信度評価関数を定義する===>詳細は「GavaOne概説書」を参照。
    > yy[?x!]:=do[$xx:x!,match[(?xx,?),tanka]]; 単価表から、特定の先頭要素を持つサブ・リストを検索するフレーム文。
    > yy[('banana,'peach)];    リストtankaのサブ・リストのうち、先頭の要素が'banana,'peachのものを探す。
     (('banana,50),('peach,200))                結果を返す。

 4)if[]関数で始まるフレーム文の引数リストを取得(少し難題です!!)
    > xxx:=nil|do[push[#0,`sv],1.0];  汎用レジスター#0に、インスタンス変数値xxxを積み上げる。
    > w:=0;                           大域変数wを初期値0で定義する。
    > rule1:=if[w>0,'a];              ルール1を定義する。
    > rule2:=if[w=0,'b];              ルール2を定義する。
    > rule3:=if[w<0,'c];              ルール3を定義する。
    > zz:=do[#0:nil,p!:=if[??xxx],match[p!,$(dir['rule!])],revlist[#0]];ルール文の引数を検索する。
    > *zz;                            フレーム文zzを評価し、ルール文の引数リストを表示する。
     ((w>0,'a),(w=0,'b),(w<0,'c))
    > 'cond/zz;         apply[]関数をcond[]関数に適用する。
     'b                 w=0であるので、cond[(w>0,'a),(w=0,'b),(w<0,'c)]を評価すれば、'bを返す。

8. ユーザー定義関数と局所変数

 引数付きのユーザー定義関数を使用して、逐次処理の事例を、以下に示します。(フレーム定義の返り値は省略しています)
 仮引数の定義には、マスク変数のリストを使用します。
 ユーザー定義関数の呼出し時には、実引数リストと仮引数リストをパターン・マッチングして、実引数の値を仮引数の局所変数に与えます。


 1)リスト要素の値を2倍にする(mult[(1,2,3,4,5),2]を、逐次(再帰)処理に置換えた場合)
    > nibai[?x!]:=if[isnil[x!],nil,append[mult[head[x!],2],nibai[tail[x!]]]]; と定義する。
    > nibai[(1,2,3,4,5)];         ===> 結果リスト(2,4,6,8,10)を返す。
     (2,4,6,8,10)

 2)リスト中の特定要素を削除する(expur[4,(1,2,3,4,5,4,6)]を、逐次(再帰)処理に置換えた場合)
    > sakujo[?x!,?y!]:=if[isnil[x!],nil,append[if[head[x!]==y!,nil,head[x!]],sakujo[tail[x!],y!]]]; 
    > sakujo[(1,2,3,4,5,4,6),4];  ===> リスト(1,2,3,4,5,4,6)の要素4を除く。結果(1,2,3,5,6)を返す。
     (1,2,3,5,6)

 3)リストの長さを調べる(size[(1,2,3,4,5)]を、逐次(再帰)処理に置換えた場合)
    > mylen[?x!]:=if[isnil[x!],0,add[mylen[tail[x!]],1]]; と定義する。
    > mylen[(1,2,3,4,5)];         ===> 結果5を返す。
     5

  4)リストの数値要素を集計する(パターン・マッチングを用いた再帰処理)
    > mysum[(?x!,??y!)]:=do[init[%0:0],if[islist[x!],mysum[x!],if[isdigit[x!],incr[%0,x!]]],if[isnil[y!],break[%0],mysum[y!]]];
    > mysum[(1,2,(3,4),5)];       ===> リスト(1,2,(3,4),5)の数値要素を演算レジスター%0に集計して、結果15.0を返す。
     15.0

 5)階乗を求める
    > fact[?x!]:=if[x!<=0,1,mult[x!,fact[sub[x!,1]]]]; と定義する。
    > fact[5];                    ===> 結果120を返す。
     120

 6)フィボナッチ数列(フィボナッチ数の説明は、省略)
    > fib[?n!]:=if[or[n!=1,n!=2],1,add[fib[sub[n!,1]],fib[sub[n!,2]]]];
    > exfib[?m!]:=do[@0:0,while[incr[@0]<=m!,print[concat[@0,":  ",fib[@0]],"ln"]]];
    > exfib[10];                  ===> 10までのフィボナッチ数の一覧表を表示する。

 7)ハノイの塔
      ;  3つのポール(A,B,C)があり、ポールAにある3つの円盤を、ポールBへ移す(詳細説明は、省略)
    > hanoipr[?n!,?A!,?B!]:=print[concat[n!,"番目の円盤を, ",A!,"から",B!,"へ移動"],"ln"];
    > hanoi[?n!,?A!,?B!,?C!]:=if[n!>0,do[hanoi[sub[n!,1],A!,C!,B!],hanoipr[n!,A!,B!],hanoi[sub[n!,1],C!,B!,A!]]];
    > hanoi[3,'A,'B,'C];          ===> 円盤枚数3枚のケースの手順を表示する。

  8)素数列リストの生成(「エラストテネスのふるい」による素数判定をもとに、素数列リストを生成する)。
    > sosu[?n!]:=if[n!>=2,do[m!:'int/sqrt[n!],while[m!>1,if[mod[n!,m!]=0,break[m!],decr[m!]]],if[`dv=1,n!]]];
    > ss[?x!,?y!]:=do[if[x!>=y!,break["input_error"]],gen[x!,'succ/sub[y!,x!],1,sosu[`gv]]];
    > ss[10,30];                  ===> 10〜30の間に含まれる素数を要素とする素数列リストを生成・表示する。
     (11,13,17,19,23,29)

9. ユーザー定義関数と可変長引数

 システム関数のうち、add[]などの四則演算関数、do[]などの逐次処理関数を除けば、システム関数の引数は、
 仮引数における項の位置(左から何番目か)によって、引数の働き方が決っています。
 しかし、ユーザー定義関数の場合には、仮引数各項の役割をあらかじめ決めずに、実引数をユーザー定義関数に 渡したい場合があります。
 このための「可変長引数」機能を、次の事例によって示します。


 1)「仮引数なし」ユーザー定義関数の呼出し(実引数の値が、ユーザー定義関数のインスタンス変数に与えられることを理解します)
    > myplus:='sum/`sv;     実引数の数列を集計する(システム変数`sv は、このフレーム文のインスタンス変数値を参照する)。
    > 'myplus/'iota/5;      1〜5を集計して、結果15を返す。(高階関数中置型をうまく用いるのが、GavaOne流プログラミングです)
     15
    > myplus;               インスタンス変数myplus の値を参照すれば、実引数リスト(1,2,3,4,5)が入っているのが分かる。
     (1,2,3,4,5)

  2)「仮引数あり」ユーザー定義関数の呼出し(実引数リストの先頭要素と後続要素を、仮引数?x!と??y!で取り出します)
    > myfunc[?x!,??y!]:=func[x!,y!];   仮引数(リスト前置の関数名と後続する引数データ)から、処理関数を生成する。
    > 'myfunc/('add,1,2,3,4,5);        リスト('add,1,2,3,4,5) ===> add[1,2,3,4,5]
     add[1,2,3,4,5]
    > 'eval/'myfunc/('add,1,2,3,4,5);  生成された処理関数を評価して、正常に生成されたことを確認する。
     15

10. 集合演算と高階関数

 リストの編集は、かなりのケースをシステム関数で行うことができますが、あらかじめ用意されたシステム関数では
 編集しずらい場合もあります。その代表例として、「集合演算」を挙げることができます。
 「和集合」と「積集合」の編集処理を、高階関数を用いたユーザー定義関数の作成事例でご紹介します。
 高階関数中置型を用いて短く定義するのが、GavaOne流です。(再帰処理による定義と、高階関数による定義を比較してみてください)


 1)「和集合(union)」ユーザー定義関数
    > union[?x!,?y!]:=cond[(isnil[x!],y!),(assoc[head[x!],y!],union[tail[x!],y!]),(1,union[tail[x!],append[head[x!],y!]])];
     'union
    > union[(2,1,4,3),(5,2,6,4)];    再帰処理によって、リスト(2,1,4,3)と(5,2,6,4)の和集合を求める。
     (3,1,5,2,6,4)                   結果リストを表示する。(x!の要素のうち、y!の要素と同じものを削除して、連結している)
    > myunion[?x!,?y!]:=adjust[x!,extract[~('add/'equal/^(x!,y!)),y!]];   中置型演算子/^は、関数cross[]の中置型表記です。
     'myunion
    > myunion[(2,1,4,3),(5,2,6,4)];  高階関数中置型を用いて、リスト(2,1,4,3)と(5,2,6,4)の和集合を求める。
     (2,1,4,3,5,6)                   結果リストを表示する。(y!の要素のうち、x!の要素と同じものを削除して、連結している)

 2)「積集合(intersection)」ユーザー定義関数
    > intsec[?x!,?y!]:=cond[(isnil[x!],nil),(assoc[head[x!],y!],append[head[x!],intsec[tail[x!],y!]]),(1,intsec[tail[x!],y!])];
     'intsec
    > intsec[(2,1,4,3),(5,2,6,4)];   再帰処理によって、リスト(2,1,4,3)と(5,2,6,4)の積集合を求める。
     (2,4)                           結果リストを表示する。(x!とy!の共通要素を取り出す)
    > myintsec[?x!,?y!]:=extract['add/'equal/^(x!,y!),y!];                中置型演算子/^は、関数cross[]の中置型表記です。
     'myintsec
    > myintsec[(2,1,4,3),(5,2,6,4)]; 高階関数中置型を用いて、リスト(2,1,4,3)と(5,2,6,4)の積集合を求める。
     (2,4)                           結果リストを表示する。(x!とy!の共通要素を取り出す)

11. 大域変数の静的評価と階層構造

 インスタンス変数値を変数名で参照する場合、変数には、インスタンス変数値そのものを与える(複製する)のではなく、
 インスタンス変数値のあり場所を指し示すポインター(フレーム番号)を与え、インスタンス変数値を参照します。
 すなわち、参照する側からは、ポインターで参照される側のフレーム番号を指し示すのみですので、
 参照される側のインスタンス変数値が、参照する側と関係なしに変更されると、参照する側のインスタンス変数値も
 自動的に変わります。


 1)インスタンス変数の静的評価と階層構造(静的評価は、「再評価」と「値参照」を兼ねていることを理解します)
    > 四季:=list[`sn, *春, *夏, *秋, *冬]; 「四季」は、春夏秋冬からなっていることを定義する。
     '四季                 システム変数`snは、フレーム自身のフレーム名を取得するためのシステム変数である。
    > 春:=list[`sn, '桜, '入学];           「春」の思い出は、桜と入学で代表される。
     '春
    > 夏:=list[`sn, '海, '夏休み];         「夏」の思い出は、海と夏休みで代表される。
     '夏
    > 秋:=list[`sn, '柿, '読書];           「秋」の思い出は、柿と読書で代表される。
     '秋
    > 冬:=list[`sn, '雪, '正月];           「冬」の思い出は、雪と正月で代表される。
     '冬
    > *四季;                       「四季」の思い出(インスタンス変数値)は、次のリストで表わされる。
     ('四季,('春,'桜,'入学),('夏,'海,'夏休み),('秋,'柿,'読書),('冬,'雪,'正月)) 
    > 秋:('秋, '紅葉, '読書);      「秋」の思い出(インスタンス変数値)を、('秋, '紅葉, '読書)に変更する。
     秋
    > 四季;                        「四季」の思い出(インスタンス変数値)は、次のリストに変わる。
     ('四季,('春,'桜,'入学),('夏,'海,'夏休み),('秋,'紅葉,'読書),('冬,'雪,'正月)) 
    > *四季;      再評価すれば、フレーム文「秋」のフレーム定義関数は変わっていないので、思い出リストは元に戻る。
     ('四季,('春,'桜,'入学),('夏,'海,'夏休み),('秋,'柿,'読書),('冬,'雪,'正月)) 

 2)インスタンス変数値の検索(階層構造をたどって、深さ優先探査をします)
    > 四季;                                      「四季」の思い出(インスタンス変数値)を、改めて表示する。
     ('四季,('春,'桜,'入学),('夏,'海,'夏休み),('秋,'柿,'読書),('冬,'雪,'正月)) 
    > 秋:=list[`sn, *秋.果物, *秋.読書];         「秋」の思い出を、再定義する。
     '秋
    > 秋.果物:=list[`sn, '柿, '梨, 'ブドウ];     「秋.果物」の思い出を、定義する。
     '秋.果物
    > 秋.読書:=list[`sn, '歴史物, 'ミステリー];  「秋.読書」の思い出を、定義する。
     '秋.読書
    > *四季;                                       再定義した結果の「四季」の思い出を再評価し、表示する。
     ('四季,('春,'桜,'入学),('夏,'海,'夏休み),('秋,('秋.果物,'柿,'梨,'ブドウ),('秋.読書,'歴史物,'ミステリー)),('冬,'雪,'正月)) 
    > 思い出:=nil;                                 検索した「思い出」を入れる変数を定義する。
     思い出
    > myfind[?x!,?y!]:=do[if[islist[y!],myfind[x!,head[y!]]],if[head[y!]==x!,break[思い出:tail[y!]]],if[~(isnil[y!]),myfind[x!,tail[y!]]]]];
     'myfind                                       検索のためのフレーム文を定義する。
    > do[*四季,myfind['秋.読書,四季],思い出];    「秋.読書」の思い出を検索し、結果を表示する。
     ('歴史物,'ミステリー)

12. 大域変数名の区分表記

 インスタンス変数名(フレーム名)を、ドット(.)で区分けすることによって、フレーム文が持つ意味(または概念)を、
 上位概念・下位概念等に区分表記することができます。
 例えば、「太郎の父」は、「父.太郎」と区分表記できます。
 その事例を、次に掲げます。


 1)親子関係をフレーム文で表現します。(フレーム定義の返り値は省略しています)
    > titi.ichiro:='taro;     'ichiro の父は、'taro  である。
    > titi.jiro:='taro;      'jiro   の父は、'taro  である
    > haha.ichiro:='hanako;    'ichiro の母は、'hanakoである。
    > haha.jiro:='hanako;     'jiro   の母は、'hanakoである。    
    > titi.taro:='kenji;      'taro   の父は、'kenji である。
    > haha.taro:='kiku;      'taro   の母は、'kiku  である。
    > titi.hanako:='sigeru;    'hanako の父は、'sigeruである。
    > haha.hanako:='fumi;          'hanako の母は、'fumi  である。
    > titi.sigeru:=nil;            'sigeru の父は、いない。
    > haha.sigeru:='chiyo;         'sigeru の母は、'chiyo である。
    > haha.fumi:='yuri;            'fumi   の母は、'yuri  である。

 2)親子関係を検索するフレーム文を定義し、親子検索します。(フレーム定義の返り値は省略しています)
    > rw[?x!,?y!]:=do[concat[x!,".",y!],if[fno[`dv]>0,'var/`dv]];   親子関係のフレーム文を探す。
    > titi[?y!]:='eval/rw[`sn,y!];         子y!の父を探す。
    > haha[?y!]:='eval/rw[`sn,y!];         子y!の母を探す。
    > oya[?y!]:=do[list[titi[y!],haha[y!]],#1:adjust[`dv],if[isnil[#1],push[#0,y!]],#1];  親とは、子y!の父と母である。
    > sobo[?y!]:='adjust/'haha//oya[y!];   祖母は、親の母である。
    > sofu[?y!]:='adjust/'titi//oya[y!];   祖父は、親の父である。
    > choro[?y!]:=do[#0:nil,#2:oya[y!],while[~(isnil[#2]),#2:'adjust/'oya//#2],'uniq/'pack/#0]; 子y!の長老を探す。
    > kw[?w!]:=if[isnil[w!],nil,'last/sep[w!,"."]]; フレーム名w!より、末尾のフレーズ(phrase)を抽出する。
    > ko[?z!]:=do['connect/list[dir['titi.!],dir['haha.!]],search[z!,`dv],'adjust/'kw//`dv]; 親z!の子を求める。
    > mago[?z!]:='adjust/'ko//ko[z!];      z!の孫を求める。(孫とは、子の子である)
    > sofu['ichiro];                       子'ichiroの祖父を探す
     ('kenji,'sigeru)
    > sobo['jiro];                         子'jiroの祖母を探す
     ('kiku,'fumi)
    > choro['ichiro];                      子'ichiroの長老(血縁の高齢者)を探す
     ('chiyo,'kenji,'kiku,'yuri)
    > ko['taro];                           'taroの子を探す
     ('ichiro,'jiro)
    > mago['fumi];                         'fumiの孫を探す
     ('ichiro,'jiro)

13. 高階関数と情報検索

 高階関数による論理演算の応用として、情報検索の事例を掲げます。
 論理演算の元になっている処理(横探査)は、次の二つのコーディング例で表わすことができます。

 ・リスト(2,4)の各要素が、リスト(3,2,1,4,5)の要素に含まれているか、を調べる
    'and/'add//'equal/^((2,4),(3,2,1,4,5));  を評価すれば、正数(真)を返す。
 ・リスト(6,4)の要素のいずれかが、リスト(3,2,1,4,5)の要素に含まれているか、を調べる
    'or/'add//'equal/^((6,4),(3,2,1,4,5));   を評価すれば、正数(真)を返す。

 1)検索の対象となる事実データをフレーム文で表現します。(フレーム定義の返り値は省略しています)
    > likes.taro:=('orange,'banana,'apple);   taroの好きな果物は、orange,banana,appleである。
    > likes.hanako:='banana;                  hanakoの好きな果物は、bananaである。
    > likes.ichiro:=('apple,'banana,'peach);  ichiroの好きな果物は、apple,banana,peachである。
    > likes.jiro:=('banana,'grape,'apple);    jiroの好きな果物は、banana,grape,appleである。
    > likes.megumi:=('orange,'grape,'peach);  megumiの好きな果物は、orange,grape,peachである。

  2)上記1)の事実データに対し、次の検索(論理積を用いた検索)を行う。
    > sch-and[?x!,?y!]:='and/'add//'equal/^(x!,y!); リストx!の要素が、リストy!に含まれているかをチェックする。
     'sch-and
    > mysch-and[?x!]:=do[search[?y!,dir['likes.!],sch-and[x!,y!]],'adjust/'last//'sep//distr[`dv,"."]];
     'mysch-and                               フレーム名が、"likes."ではじまる事実データを対象に検索する。
    > mysch-and[('banana,'apple)];            banana,appleともに好きな人を探す。
     ('taro,'ichiro,'jiro)                    結果を表示する。

 3)上記1)の事実データに対し、次の検索(論理和を用いた検索)を行う。
    > sch-or[?x!,?y!]:='or/'add//'equal/^(x!,y!); リストx!の要素のうち、いずれかが、リストy!に含まれているかをチェックする。
     'sch-or
    > mysch-or[?x!]:=do[search[?y!,dir['likes.!],sch-or[x!,y!]],'adjust/'last//'sep//distr[`dv,"."]];
     'mysch-or                                フレーム名が、"likes."ではじまる事実データを対象に検索する。
    > mysch-or[('grape,'orange)];             grape,orangeのいずれかが好きな人を探す。
     ('taro,'jiro,'megumi)                    結果を表示する。

14. 多値受け渡しとカプセル化

 関数呼出しにおいて、一般的に複数の引数を与えて関数適用指示を行うのに対し、関数適用の結果である 返り値(または、戻り値とも呼ぶ)が、ひとつ(リストも一つと数える)であることは、処理命令をコード化する上で、 とかくの不便を生ずるため、返り値の要素を複数の変数に分け与えることによって、その不便を解消することが行われます。
GavaOne言語では、次の二つのケースを掲げます。

 ・ 返り値がリストである場合に、そのリストの特定要素を、次に続く処理のために、複数の局所変数に与える。
   いわゆる「多重代入」と言われるもので、次の関数set[]中置型の例で表わされる。
      > (x!,y!):list[1,2,3,4,5];  局所変数x!に1を、局所変数y!に2を代入し、結果として、リスト(1,2,3,4,5)を返す。
       (1,2,3,4,5)                 右辺のリストに占める要素の位置が変われば、局所変数x!とy!に代入される値も
                                   変わってくることに留意する。

 ・ ユーザー定義関数の仮引数変数(マスク変数)を、ユーザー定義関数に対するデータ入力であると同時に、処理結果の
   出力変数としても扱う。(同様の機能は、prolog言語にもあります ===> 関数引数内での値の授受)
      > myincr[?x!]:=if[incr[x!]<10,-1,1];  仮引数x!の値を1増やし、その結果が10より小ならば、-1を返す、
       'myincr       その結果が10より大ならば、1を返す。(関数myincr[]は、説明のためのもので、特に意味はありません)
      > y:=5;        変数yを、5と定義する。
       'y
      > myincr[?y];  マスク変数?yと?x!をパターンマッチングして、変数yの値を局所変数x!に与え、関数myincr[]を評価する。
       -1            関数myincr[]復帰時に、マスク変数?x!のインスタンス値をマスク変数?yに戻す。(変数yの値は、6になる)
                     ===> 実引数にマスク変数を指定したときの働きを理解します。
      メイン・フレームからサブ・フレームを呼出し評価する場合、メイン・フレームとサブ・フレームの入出力(データ授受)
      の対応関係は、メイン・フレームの実引数とサブ・フレームの仮引数で指定したマスク変数(仮引数変数)
      の対応関係のみによって表わされる。すなわち、サブ・フレームは、メイン・フレームから委託された処理を、
      自身の仮引数変数に表わされた局所変数にのみ注力して行えば良いことになる。
      サブ・フレームの内部処理において、大域変数によるデータ授受がなく、仮引数変数によるデータ授受の約束事を
      守りさえすれば、サブ・フレーム外の環境を考慮することなく、自由に処理できることを意味する(カプセル化)。

 以上の「多値受け渡し」処理の例を、「ユークリッドの互除法による最大公約数計算」を借りて、以下のとおり掲げます。
 なお、「ユークリッドの互除法」の説明は、省略します。
 1)多重代入による例
    > ugrid1[?m!,?n!]:=list[n!,mod[m!,n!]];     関数mod[m!,n!]は、m!をn!で割った余りを求める関数です。
     'ugrid1
    > mygcd1[?x!,?y!]:=do[if[x!<y!,break["input-error"]],while[y!>0,(x!,y!):ugrid1[x!,y!]],x!];
     'mygcd1
    > mygcd1[323,221];    整数323と221の最大公約数を計算します。
     17                   結果17を表示します。

  2)関数引数内での値の授受による例
    > ugrid2[?m!,?n!]:=do[s!:mod[m!,n!],m!:n!,n!:s!];    m!とn!は、入力変数であると同時に、出力変数でもある。
     'ugrid2              変数s!のように、サブ・フレーム内でのみ使用する変数を、「局所自由変数」と言う。
                          局所自由変数s!は、サブ・フレーム処理終了後も、処理終了時の値を保持する。
    > mygcd2[?x!,?y!]:=do[if[x!<y!,break["input-error"]],while[y!>0,ugrid2[?x!,?y!]],x!];
     'mygcd2
    > mygcd2[323,221];    整数323と221の最大公約数を計算します。
     17                   結果17を表示します。

15. 変数の初期化と局所関数の定義

 変数を数値リストで初期化する場合は、次の二つの変数定義の方法があります。

 ・ フレーム文の右辺に、直接、数値リストを与える。
    > x:=(1,3,5,7,9,11,13,15,17,19);   インスタンス値がないので、フレーム文本体(ボディ)のリストが
                                       そのまま、変数x の値となる。
    > x;                               変数x の値を参照する。
     (1,3,5,7,9,11,13,15,17,19)

 ・ フレーム文の右辺を、数値リストを生成する関数定義文にする。(関数定義文を評価すると数値リストをつくり出す)
    > y:=gen[1,10,2];                  関数gen[]を用いる。(初項1、階差2で、要素数10の数値リストを生成する)
     'y
    > *y;                              変数y を静的評価する。
     (1,3,5,7,9,11,13,15,17,19)
    > y;                               変数y のインスタンス値を参照する。
     (1,3,5,7,9,11,13,15,17,19)

 以下、関数gen[]の4番目の引数を入力することにより、より複雑な数値リストを生成することができます。
 1)関数gen[]の4番目の引数に、数値リストの要素を選別する関数を指定する。
    > *(g1:=gen[1,10,2,if[mod[`gv,3]=0,0,1]]);  生成された数値要素のうち、3の倍数値を除いたリストを返す。
     (1,5,7,11,13,17,19)               フレーム'g1を定義・静的評価して、インスタンス値を表示する。

  2)数値リストの要素を選別する局所関数h![]を定義し、用いる。
    > *(g2:=do[init[h![?x!]:=if[mod[x!,3]=0,0,1]],gen[1,10,2,h![`gv]]]);
     (1,5,7,11,13,17,19)               フレーム'g2のインスタンス値を表示する。

  3)数値リストの要素を選別する局所関数h![]を定義し、更に、局所自由変数z!を用いる。(少々難解です!!)
    > *(g3:=do[z!:0,init[h![?x!,?y!]:=if[mod[x!,3]=0,0,incr[y!]]],gen[1,10,2,h![`gv,?z!]]]);
     (1,5,7,11,13,17,19)               フレーム'g3のインスタンス値を表示する。
    > eval[nop[z!],'g3];               局所自由変数z!の値を参照する。(仮引数変数y!は、入出力変数であることに留意)
     7                                 フレーム'g3のインスタンス値の要素数を表示する。

16. 代行フレームと変数束縛

ある特定の機能を、ある条件下(例えば、処理の初回のみ)で他のフレームに委託したい場合があります。 
このとき、処理を委託されたフレームを「代行フレーム」と言います。
また、「変数束縛」と言えば、一般的に、「変数に値を代入する」、「変数に値を引き当てる」、或いは、「変数同志を結びつける」 の三つの意味がありますが、 GavaOne言語では、変数束縛を、「変数同志を結びつける」意味に限定しています。
具体的には、「指定された変数同志が、互いに束縛を受けている間、同じ値をとる」ことを意味します。

上記二つの機能を、以下の事例で説明します。
 1)代行フレームの指定と解除
    > a:=10;              フレーム'aを定義する(返り値は省略)。
    > b:=20;              フレーム'bを定義する(返り値は省略)。
    > c:=30;              フレーム'cを定義する(返り値は省略)。
    > proxy['a,'b];       関数proxy[]で、フレーム'bを、フレーム'aの代行フレームに指定する。
     1
    > a;                  インスタンス変数aの代行フレーム指定前の値は、10であるが、
     20                   代行フレーム指定後のaの値は、インスタンス変数bの値を受けて、20となる。
    > proxy['a,nil];      フレーム'aの代行フレーム'bを解除する。
     1
    > a;                  ここで、インスタンス変数aの値は、フレーム'a 定義時の値10に戻る。
     10
    > proxy[('a,'b),'c];  次に、フレーム'cを、フレーム'aとフレーム'bの代行フレームに指定する。
     2        これは、フレーム'aとフレーム'bが、フレーム'c を仲立ちに、互いに束縛している形になったとも言える。
    > a;                  aの値は、インスタンス変数cの値30になる。
     30
    > b;                  bの値も、インスタンス変数cの値30になる。
     30
    > proxy[('a,'b),nil]; フレーム'aとフレーム'bの代行フレーム'cを解除する(aとbの束縛関係を解除)。
     2
    > b;                  bの値は、束縛前の値20に戻る。
     20

  2)変数束縛の指定と解除(「無名フレーム」という隠されたフレームを代行フレームとした変数束縛と解除)
    > a:=10;              フレーム'a(仮にフレーム番号を304とする)を定義する(返り値は省略)。
    > b:=20;              フレーム'b(仮にフレーム番号を305とする)を定義する(返り値は省略)。
    > bind['a,'b];        関数bind[]で、フレーム'aとフレーム'bの代行先を、隠された無名フレームとする。
     (304,305)            束縛したインスタンス変数のフレーム番号のリストを返す。
    > b:50;               仮に、インスタンス変数bに、50を代入する。
     50
    > a;                  インスタンス変数aの値も、50に変わる。
     50
    > unbind['a,'b];      関数unbind[]で、フレーム'aとフレーム'bの束縛関係を解除する。
     (304,305)            解除したインスタンス変数のフレーム番号のリストを返す。
    > a;                  インスタンス変数aの値は、束縛前の値10に戻る。
     10
            (注)「無名フレーム」とは、フレーム番号のみでアクセス可能な(名前のない)フレームで、
                   フレームの定義文によらず、評価(実行)時に確保される(隠された)フレームです。

17. 単一化(ユニフィケーション)アルゴリズムの実装

代行フレームによる変数束縛応用の一つとして、一階述語論理における単一化アルゴリズムのシステム化を紹介します。
GavaOne言語では、単一化アルゴリズムの重要性を考え、基本関数として組込み関数unify[]を実装しています。

 Lisp言語では、単一化アルゴリズムをプログラミングする上で、次のような共通の課題があります。
 ・ パターン変数名(GaveOne言語の場合は、マスク変数名)の重複を、如何に避けるか。
 ・ パターン変数値の保存・参照をどのようにするか(連想記憶リストによるか、ハッシュ・テーブルによるか)。

 GavaOne言語では、これらの課題を、次のようなGavaOne言語の特徴で回避しています。
 ・ 大域変数名は、フレーム名を兼ね、ハッシュ・テーブルで管理されている(フレーム名に重複はない)。
 ・ マスク変数(局所自由変数)名の重複を、そのマスク変数が帰属する大域変数の範囲内で許していない。
 ・ マスク変数(局所自由変数)の値は、単一化処理終了後に意図的に変更しなければ、処理終了時の値を保持する。
      (注)ここで言う「局所自由変数」とは、サブ・フレームの仮引数として定義した局所変数ではなく、
            単独に定義した局所変数を言い、サブ・フレームの処理終了後も、終了時の値を保持する。
            ===> Lisp言語で一般的に言う局所自由変数とは、意味が異なることに留意する。

 更に、GavaOne言語は、他のLisp言語にない次の新しい役割を、組込み関数unify[]に担わせています。
 ・ 局所自由変数の値をフレーム間で受け渡す機能(複数のフレームにまたがる横断的処理が可能になる)。
      (注)「複数のフレームにまたがる横断的処理」とは、「各フレームの振舞いを監視するモニタリング処理」、
            「フレームのネットワーク結合」等を言う。
            ===> 「フレーム処理のカプセル化」とは、対極の概念である。

以下、単一化関数unify[]の代表機能を紹介します。
 1)論理式を表わす二つのリストを引数として、単一化の可能性をチェック
    > do[x!:=nil,y!:=nil,unify[(1,?x!,3),(1,2,?y!)]]; リスト(1,?x!,3)と(1,2,?y!)をパターン・マッチングする。
     3                                              マッチした要素数3(真)を返し、単一化可能であることを示す。
                                                    局所自由変数x!,y!の値は、x!=2,y!=3になる。
                                                    変数x!,y!をフレーム内で事前定義して、明示的に変数x!,y!を初期化している。
    > do[x!:=nil,y!:=nil,unify[(1,?x!,3),(1,?y!,?y!)]]; リスト(1,?x!,3)と(1,?y!,?y!)の単一化可能性をチェックする。
     3                                              マッチした要素数3(真)を返し、単一化可能であることを示す。
                                                    局所自由変数x!,y!の値は、x!=3,y!=3になる。
                                                    x!,y!は、互いに束縛関係になることに留意する。
    > do[x!:=nil,y!:=nil,unify[(1,?x!,3),(1,(2,?x!),?y!)]]; リスト(1,?x!,3)と(1,(2,?x!),?y!)の単一化可能性をチェックする。
     0                                              関数unify[]の第1引数第2項の変数x!が、第2引数第2項に含まれているので、
                                                    単一化不可であり、0(偽)を返す。
    > do[x!:=nil,y!:=nil,z!:=nil,unify[(?x!,?y!,3),(?y!,?z!,?z!)]];リスト(?x!,?y!,3)と(?y!,?z!,?z!)の単一化可能性をチェックする。
     3                                              マッチした要素数3(真)を返し、単一化可能であることを示す。
                                                    局所自由変数x!,y!,z!の値は、x!=y!=z!=3になる。
                                                    x!,y!,z!は、互いに束縛関係になることに留意する。
    > do[x!:=nil,y!:=nil,unify[(1,?x!,3),(1,(2,?y!),?y!)]]; リスト(1,?x!,3)と(1,(2,?y!),?y!)の単一化可能性をチェックする。
     3                                              マッチした要素数3(真)を返し、単一化可能であることを示す。
                                                    局所自由変数x!,y!の値は、x!=(2,3),y!=3になる。
                                                    評価終了後に、システム変数`ufrを参照すれば、リスト(1,(2,3),3)を返す。

 2)局所自由変数の値をモニタリング
   > shoyo[?x!,?y!]:=if[y!>0,do[s!:'int/div[x!,y!],r!:sub[x!,mult[y!,s!]],(s!,r!)],0];
    'shoyo                                          整数x!を整数y!で割った値(商)と余りを求める(説明のためのフレーム例である)。
   > shoyo[100,17];                                 100÷17を計算し、リスト(商,余り)を計算し、結果を表示する。
    (5,15)
   > do[unify['(?s!,?r!),'(?s!,?r!),,,'shoyo],(s!,r!)]; フレーム'shoyoの局所自由変数s!,r!を、自フレームs!,r!に取込み、
    (5,15)                                          自フレームs!,r!の値をリストの形で表示する。
                                                    関数unify[]の第1引数変数s!,r!と第2引数変数s!,r!は、互いに別物であることに留意する。

 3)後向き推論を用いた情報検索(超難解です!! --> 健康にわるいですから、あまり考え込まないでください)
      Lisp言語では、一階述語論理における論理式をリスト形式で表現するが、GavaOne言語でも同様に論理式をリスト形式で表現する。
  次の論理式で表わされる事実と推論規則の集まりの中から、論理式(リスト)で表わされる質問に対する回答を求める。
   > r1:=(('like 'taro 'apple));                    太郎は、リンゴが好きだ。
    'r1
   > r2:=(('like 'taro 'grape));                    太郎は、ブドウが好きだ。
    'r2
   > r3:=(('like 'hanako 'apple));                  花子は、リンゴが好きだ。
    'r3
   > r4:=(('like 'hanako 'orange));                 花子は、オレンジが好きだ。
    'r4
   > r5:=(('like 'taro ?x!),('like ?x! 'orange));   太郎は、オレンジが好きな人が好きだ(推論規則)。
    'r5
   > *(uu:=dir['r!]);                               事実と推論規則を表わすフレーム名のリストをつくる。
    ('r1,'r2,'r3,'r4,'r5)

    次に検索するための仕組み(フレーム)を定義する(画面表示の都合上、一つのフレーム文を、折り返し表示している)。
    なお、検索ロジックでは、再帰的フレーム探査を考慮していない。
   > unif[?u!,?x!,?y!,?xx!,?yy!,?z!]:=unify[x!,y!,if[~(isnil[z!]),query[uu,yy!,z!],1],
             if[and[`uf>0,isnil[z!]],do[if[~(isnil[eval[nop[x!],yy!]]),tpr[x!,"   -->",yy!]],1],0],xx!,yy!];
    'unif
   > query[?u!,?yy!,?z!]:=if[~(isnil[u!]),do[xx!:head[u!],x!:head[$(xx!)],
             if[~(isnil[x!]),do[if[xx!<>yy!,do[unif[u!,x!,y!:head[z!],xx!,yy!,tail[z!]],
             if[`uf>0,query[uu,xx!,tail[$(xx!)]]]]],query[tail[u!],yy!,z!]]]]];
    'query
   > ask:=query[uu,'ask,(`sv)];
    'ask

    以上の事実・推論規則フレームと検索フレームに対し、次の検索指示を行い、結果を表示する。
   > 'ask/('like,'taro,?x!);                        太郎が好きなもの(ひと)は、なん(だれ)ですか?
    --> 'apple                                      太郎が好きなもの(ひと)は、リンゴ、ブドウに、花子です。
    --> 'grape
    --> 'hanako

     (注)「後向き推論を用いた情報検索」システムの作成に当っては、参考文献 5)の第13章を参考にしています。

18. 応用ソフトウエアの作成とバッチ型処理

「繰返し利用型のソフトウエア」を作成するには、「バッチ型処理」を利用します。

  バッチ型処理の特徴と要領は、次のとおりです。
 ・ フレーム文を、処理の順序に従って、テキスト・ファイルに入力する。
 ・ 上記のテキスト・ファイルを、関数exec[]でシステムに取込み、評価(実行)できる。
 ・ テキスト・ファイルにフレーム文を入力するに当っては、1フレーム文を複数行で入力でき、
      適宜、注釈文字列を入れることができる。
      ===> 「;(セミコロン)」または「 ... (スペース+ドット3個以上)」の右側に入力した文字列は、注釈文字列とみなす。
 ・ 「`end(バッククオート+end)」を入力して、フレーム文入力の終了を宣言する。

 次に、「階乗計算」を参考に、バッチ型処理用テキスト・ファイル(以下、バッチ・ファイルと言う)の入力例を示します。
 なお、「>>」は、バッチ・ファイル入力編集ソフトの入力要求プロンプトとします。

   >> fact[?x!]:=       ... 仮引数変数x!は、非負の自然数をとる。
   >>    if[x!<=0,1,        ....  x!<=0 ならば、結果を1とする。
   >>       mult[x!,fact['pred/x!]] .... x!>0 ならば、x!-1を引数とする関数fact[]に、x!を乗じ、結果とする。
   >>      ];           関数if[]を締めくくる。
   >> `end;             バッチ入力データの終り

 仮に、上記バッチ・ファイルの名前を"appfact.txt"とすれば、次のとおり、会話型処理でシステムに取込み、
 評価(実行)することができます。
   > exec["appfact"];       バッチ・ファイル"appfact.txt"を読み込み、評価する(読み込んだ行数を返す)。
    4
   > ppf[];                 読み込まれたフレーム文を表示する。
    fact[?x!]:=if[x!<=0,1,mult[x!,fact['pred/x!]]];
   > fact[5];               5の階乗を求める。
    120

19. 外部データ・ファイルによるデータ入出力

応用ソフトウエアでは、処理する都度、多くの入力データを会話型で入力することは面倒でもあり、 特に、他システムの処理結果を受けて繰返し処理する入力データは、外部ファイルから取り込む必要があります。
 また、応用ソフトウエアの処理結果を受けて、他システム向けにデータを出力することもあります。
このような外部ファイルによるデータ入出力は、「レコード単位のデータ入出力」「CSV形式によるデータ一括入出力」の二つの形式で行います。

 次に、上記の二つの形式について、データ入出力の事例を示します。
 1)レコード単位のデータ入出力
   > do[while[fread[xyz!,"in_data"]>0,print[xyz!,'ln]],fclose["in_data"]]; テキスト・ファイルin_data.txtを、レコード単位に
                                                     読み込んで、その内容をプリントする。

   > do[x!:"Hello world!",y!:10,while[decr[y!]>=0,fprint[x!,"out_data"]],fclose["out_data"]];
                                                     テキスト・ファイルout_data.txtに、文字列"Hello world!"を
                                                     10レコード分書き出す。

 2)CSV形式によるデータ一括入出力
  事例表示の前に、説明のためのバッチ・ファイル"csv_indata.csv"を、次のとおり作成します。
   >> "namae","sincho","taiju"
   >> "taro",170,65
   >> "hanako",165,50
    OAツールとのインターフェイスにおいては、CSV形式によるデータ授受がよく行われます。
    上のバッチ・ファイルを用いて、次に「CSV形式によるデータ一括入出力」の事例を紹介します。
   > x:=import["csv_indata"]; データ入力のフレーム文を定義する(CSV形式データ・ファイル"csv_indata.csv"を読み込み、
                            データをリストに変換、変換結果をインスタンス変数x に入れる)
    'x
   > *x;                    インスタンス変数x を評価すれば、結果リストを返す。
    (("namae","sincho","taiju"),("taro",170,65),("hanako",165,50))

   > y:=export["csv_outdata",x]; データ出力のフレーム文を定義する(インスタンス変数x にあるリストを変換して、
                                 データ・ファイル"csv_outdata.csv"に書き出す)
    'y
   > *y;                         インスタンス変数y を評価すれば、バッチ・ファイル"csv_outdata.csv"に
                                 インスタンス変数x にあるリストのサブ・リストを行データに変換して書き出す。
    3                      評価結果(書き出し行数3)を返す。

20. 深さ優先探査と大域脱出

関数引数の型チェックは関数評価の直前に行いますが、引数値の範囲チェックと処理ロジックの正当性などは、 本ソフトウエア利用者が、応用ソフトウエア上でチェックする必要があります。
 また、関数do[]や関数while[]の引数評価において、本筋の処理手順を中断して、例外的な値を戻り値にしたい場合が あります。
 更に、その応用ソフトウエアにとって、所期の目的を得られない事態、または、データ検索などで所期の目的値を得て、 それ以降の処理を中断し、コントロールをトップレベルに戻したい場合があります。

 上記に該当する代表的なものを、フレーム定義文作成に即して整理すれば、次のとおりとなります。
 ・ 条件判定文で、入力値をチェックし、エラー・メッセージを表示、再入力を促す。
 ・ 逐次評価関数do[]の引数を左から順に評価する過程で、最後尾引数の評価値ではなく、途中の評価値を戻り値とする。
 ・ 繰返し評価関数while[]において、無限ループに陥らないように、関数評価の繰返し回数に制限を加える。
 ・ 入れ子構造で定義されたフレーム文において、大域変数値または局所変数値の異常値を検出し、処理を中断する。
 ・ 関数呼出しが木構造になっている応用ソフトウエアにおいて、低レベルのフレーム定義文から、
      トップレベルに一気にコントロールを戻す。
  これらの処理は、関数beak[],return[],exit[]で対応します。

  関数beak[],return[]の働きは、手続き型言語で一般的に見られるので、容易に理解できると思いますが、
  関数exit[]の働きについては、例題を用いて、より詳しく説明してご理解を得たいと思います。

    例題として、深さ優先探査のためのフレーム定義文を掲げます。
    その前に、前提となる深さ優先探査の対象となる経路データを、木構造リストで表現します。
 ・ 経路の入り口と出口ふたつを、リスト('入り口,'出口1,'出口2)と表す。
 ・ 経路の途中で分岐し、その先が二つに分かれて、出口がふたつある場合は、
      リスト('入り口'('分岐点,'出口1,'出口2),'出口3)と表す。
 ・ 経路データを木構造リストとしてみれば、経路の入り口は「根(root)」、分岐点は「節(node)」、出口は「葉(leaf)」
      に対応する。
 ・ 木構造リストでは、根、節、葉を、一般的に重複のない番号で表現する。
       (例1)リスト('入り口'('分岐点,'出口1,'出口2),'出口3)を番号で表現すれば、
              (1,(2,3,4),5)と表わすことができる。
       (例2)より複雑な木構造リストで、根、節、葉を説明すれば、
              (1,(2,(3,4,5),(6,7,8),9),(10,(11,(12,13,14),15),16))における根、節、葉にあたる要素番号は、
              そろぞれ、次のようになる。
                    根(root) ==> 1
                    節(node) ==> 2,3,6,10,11,12
                    葉(leaf) ==> 4,5,7,8,9,13,14,15,16

    次に、深さ優先探査のための応用ソフトウエアをバッチ入力し、バッチ・ファイル"keirotansa.txt"を
    以下のとおり作成します(「>>」は、バッチ・ファイル入力編集ソフトの入力要求プロンプトです)。
    
   >> dlst:=(1,(2,(3,4,5),(6,7,8),9),(10,(11,(12,13,14),15),16));    深さ優先探査対象の経路データ(木構造リスト)
   >> mtch[?u!,?s!]:=if[u!==s!,exit[push[#0,u!],revlist[#0]],if[@(@0)=1,push[#0,u!]]];   検索すべき番号と要素番号との比較
   >> srch[?lst!,?s!]:=do[init[#0:nil,@0:1,@(@0):0],             ... ノードスタック#0と探査の深さカウンタ@0の初期化、 
   >>   incr[@(@0)],if[@(@0)=1,if[islist[head[lst!]],exit[print["node_number nothing:"],lst!]]], ... ノードチェック
   >>   if[@(@0)>1,if[~(islist[head[lst!]]),if[head[lst!]=#0,exit[print["route_net loop?: "],head[lst!]]]]], ... ループチェック
   >>   if[~(islist[head[lst!]]),mtch[head[lst!],s!],do[incr[@0],@(@0):0,srch[head[lst!],s!]]],  ... 比較要素の選定と比較の深化
   >>   if[~(isnil[tail[lst!]]),srch[tail[lst!],s!],do[@(@0):0,decr[@0],pop[#0],nil]]];  ... 次の比較要素の取得と比較終了チェック
   >> keiro:=srch[dlst,14]; フレームkeiroを評価することで、経路データdlst を探査して、要素番号14に至る経路を、要素番号で示す
   >> `end;
    (注)上記ソフトウエアは、ユーザー定義関数の再帰呼出しによる要素番号探査とバックトラッキング機能とで
          構成されています。
          関数exit[]を評価すると、コントロールを、再帰呼出しの深みから、一気にトップレベルに移し、
          引数の評価結果を表示します。


  上記バッチ・ファイル"keirotansa.txt"を、次のとおり、会話型処理でシステムに取込み、評価(実行)することができます。
   > exec["keirotansa"]; バッチ・ファイル"keirotansa.txt"を読み込み、評価する(読み込んだ行数を返す)。
    8

   > *keiro;     フレームkeiroを評価すると、経路データdlst を探査して、要素番号14に至る経路を、要素番号リストで示す。
    (1,10,11,12,14)                   1->10->11->12->14の順に、入り口1から出口14に至る道筋を示す。

   > srch[(1,(2,3,4),((5,6),7)),7];     別経路データを直接入力して、関数srch[]を評価(チェック)すると
    node_number nothing:((5,6),7))    第1引数データに、経路の分岐点(node)の定義が欠けていることを指摘する。

   > srch[(1,(2,3,4),(5,(6,5),7)),7];   更に、第1引数(経路)データを変えて、評価(チェック)すると
    route_net loop?: 5                第1引数データに、ループ状の経路あり、その原因たる要素番号を表示する。


21. あとがき

 GavaOne言語による処理事例、お楽しみいただけたでしょうか。 
できるだけ平易に解説したつもりですが、手続き型言語の表記法で糖衣表現したとは言っても、
リストを引数とする演算は、手続き型言語を日頃使っておられる読者で関数型言語に予備知識のない方にとって、
特に違和感を感じられたと思います。
 プログラミング学習の手始めとして、手続き型言語を学ぶのが一般的であり、手続き型言語に特有の「繰返し処理」は、
現代のプログラマーにとって必修のアルゴリズムです。
筆者は、手続き型言語の学習を続けていくにつれて、プログラミングとは繰返し処理をコーディングすることである
との感覚を強めていったように思います。
基幹業務システムの開発と保守を長年行っていると、メインフレームと称するコンピュータを操作する言語にのみ
堪能になり、その言語を用いて、如何に業務改善できるか、如何に開発コストを抑えられるかに腐心する毎日となりがちです。
このような時、ダイクストラによる構造化プログラミングの考えは、プログラミングする上での教師的存在でした。
 
 この感覚に衝撃を与えたのは、バッカスのFP言語、アイバーソンのAPL言語と表計算ソフトを代表とする簡易言語の登場です。
バッカスのFP言語が持つ手続き型言語を乗り越える可能性を秘めた先進性に驚き、
アイバーソンのAPL言語には、手続き型言語とは全く異なるプログラミング・スタイルに驚き、
表計算ソフトを代表とする簡易言語の登場は、今までのホストコンピュータ中心のプログラミング・スタイルから、
パソコン中心のエンドユーザー・コンピューティング時代の到来を教えてくれました。
 パソコンの登場までの事務部門のコンピュータ関連処理は、情報システム部門の一手引き受けであり、
エンドユーザーは、その処理結果を紙で受け取るばかりでしたが、パソコンの登場とともに、両者の関係に
大きな変化が生まれました。
 ・ エンドユーザーは、従来紙で受け取っていた資料を、パソコン(または、タブレット端末)で参照する。
 ・ エンドユーザーは、ホストコンピュータにある処理済データをパソコンに取り込める。
 ・ エンドユーザーは、手許に新しい武器(ワープロソフト、表計算ソフトなど)を手に入れた。
 
 技術部門では、エンドユーザー・コンピューティングは、はるか昔から行われていたことであり、技術計算ソフトの作成、
特定分野向けのアプリケーション・ソフトの利用は、当然のことのように行われてきました。
それに対して、事務部門で多くの恩恵を受けているパソコン使用者にとって、エンドユーザー・コンピューティングは、
日常的なルーチンワークに役立つ表計算ソフト、ワープロソフトの利用に留まっているように思います。
 データウエアハウスの構築、インターネット技術の発展によって可能となったクラウドコンピューティングによって、
情報システム部門のエンドユーザーに対する支援は、かなり充実してきたかに見えます。
 しかし、エンドユーザーがもっとも知恵を必要とする経営計画などに不可避なシミュレーション計算においては、
その複雑度から、表計算ソフトの能力を超えるもの(ファイナンス関連計算、新規事業計画シミュレーション等)もあり、
いろいろな条件下での試算が可能な新しいスクリプト言語が求められていると思います。
そのような新しいスクリプト言語は、短い時間で結果を求められるが故に、次のような特徴を持つべきだと筆者は考えています。
 ・ 計算ロジックを少ない記述量で表現でき、計算ロジックの変更が容易である。
 ・ 計算ロジックをプログラムの注釈文を見ながら追跡できる。
 ・ データ解析のための多くの関数を備えている。
 ・ 計算結果(データ)を、表計算ソフトに渡すことができる。
このような新しいスクリプト言語を手に入れたエンドユーザーは、試算結果とともに、ちょっとした手作りの楽しさ
を得られるでしょう。
これが、GavaOne言語を開発する動機のひとつです。
 
 GavaOne言語の基本構造を検討するにあたって、個人的に関心のある認知脳科学の知識をベースに置きました。
プログラミングは頭に浮かぶ計算ロジックを具体化(外化)する作業であると考えれば、言語と人のインターフェイス
と言語の基本構造に認知脳科学上の知識を生かすことが不可欠であると思い、ミンスキーのフレーム理論を参考にしました
(賢明な諸氏は、GavaOne言語の「フレーム文」がフレーム理論の「エージェント」を模したものであること、にお気づきでしょう)。
そして、リスト形式がフレーム構造のデータを扱うに好都合であることから、リスト形式データを扱う新しい言語GavaOne
を開発するに至りました。
 その昔、筆者も簡易言語で経営シュミレーション・ソフトを手懸けましたが、エンドユーザーの依頼をうけて、
情報システム部門がソフトウエアの保守を行うようであれば、エンドユーザーの意図が迅速に、かつ的確に反映されるよう
システムを維持することは、困難であるとの思いを強く持っています。
理想的には、課題変更に応じて、エンドユーザー自らがシステムを開発保守(エンドユーザーコンピューティング)
することが望ましく、そのような簡易言語の登場が待たれるでしょう。
 日頃、業務システムの開発保守に心身をすり減らしている方、少しの間、開発人月の世界、オブジェクト指向の世界、
手続き型言語(繰返し処理 while(),for() )の世界を離れて、新しいプログラミングの世界に遊んでみませんか?
 
 
 
===> Top Page

(参考文献) 1) 「新しいプログラミング・パラダイム」井田哲雄編,共立出版,1989/11/10発行 2) 「続新しいプログラミング・パラダイム」井田哲雄・田中二郎編,共立出版,1990/11/25発行 3) 「APLイディオムライブラリー」IBM-APLユーザー研究会編,工学図書,1984/3/25発行 4) 「記号処理」中西正和著,朝倉書店,1978/12/15発行 5) 「Schemeによる記号処理入門」猪俣俊光・益崎真治共著,森北出版, 2003/5/15発行 6) 「心の社会」マーヴィン・ミンスキー著,安西祐一郎訳,産業図書,1990/7/20発行 7) 「コンピュータービジョンの心理」P.H.ウインストン編,白井良明・杉原厚吉訳,産業図書,1972/2発行 8) 「1977 ACM Turing Award Lecture」John Backus,IBM Research Laboratory,San Jose 9) 「JAVA言語」河西朝雄著,ナツメ社,2001/12/3発行 10) 「JAVAプログラミング BlackBook」Steven Holzner著,武藤武志監修,トップスタジオ訳,株式会社インプレス社,2002/3/21発行 11) 「標準JAVA Webデータベースプログラミング」横井与次郎著,秀和システム社,2002/9/20発行 12) 「Javaによるはじめてのアルゴリズム入門」河西朝雄著,技術評論社,2001/7/14発行 13) 「だれでもわかる数値解析入門 −理論とCプログラム−」新濃清志・船田哲男共著,2002/3/1発行 14) 「知識と推論」新田克己著,サイエンス社,2002/6/10発行 Copyright(C) 2011 木村紀介. All rights reserved.