[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2 ユーザ定義函数の書き方


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.1 ユーザ定義函数

ユーザによる函数の定義は ‘def’ 文で行う. 文法エラーは読み込み時に ある程度チェックされ, おおよその場所が表示される. 既に(引数の個数に関係なく)同名の函数が定義されている場合には, その函数は再定義される. ctrl() 函数により verbose フラグ が on になっている場合,

afo() redefined.

というメッセージが表示される. ある函数の定義において, まだ未定義の函数 を呼び出していても, 定義時にはエラーにならない. 実行時に未定義の函数 を呼び出そうとした場合にエラーとなる.

def f(X) { 
    if ( !X )
        return 1;
    else 
        return X * f(X-1);
}


def c(N)
{
    A = newvect(N+1); A[0] = B = newvect(1); B[0] = 1;
    for ( K = 1; K <= N; K++ ) {
        A[K] = B = newvect(K+1); B[0] = B[K] = 1;
        for ( P = A[K-1], J = 1; J < K; J++ ) 
            B[J] = P[J-1]+P[J];
        }
    return A;
}


def add(A,B)
"add two numbers."
{
    return A+B;
}

2 つ目の例では, 長さ N+1 のベクトル (Aとする) が返される. A[I] は長さ I+1 の配列であり, そのそれぞれの要素が ICJ を要素とする配列である.

3 つ目の例では, 引数並びのあとに文字列が置かれているが、これは Emacs-Lisp の関数定義に類似の機能で、ヘルプ用の文字列である。 この例の場合、help(add) によってこの文字列が出力される。

参照

help.

以下では, C によるプログラミングの経験がない人のために, Asir 言語 によるプログラムの書き方を解説する.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.2 変数および不定元

既に述べた通り, Asir においてはプログラム変数と不定元を明確に 区別している.

変数

大文字で始まり, アルファベット, 数字, ‘_’ からなる文字列

変数あるいはプログラム変数とは, Asir のさまざまな型の内部形式を 格納するための箱であり, 格納された内部形式が, この変数の値である. 変 数が式の要素として評価される時は, そこに収められた値に置き換えられる. すなわち, 内部形式の中にはプログラム変数は現れない. 変数は全て 0 で 初期化されている.

[0] X^2+X+1;
1
[1] X=2;
2
[2] X^2+X+1;
7
不定元

小文字で始まり, アルファベット, 数字, ‘_’ からなる文字列, またはシングルクオートで囲まれた文字列, もしくは函数形式. 不定元とは, 多項式環を構成する際に添加される変数をいう. Asir に おいては, 不定元は値をもたない超越的な元であり, 不定元への値の代入は 許されない.

[3] X=x;
x
[4] X^2+X+1;
x^2+x+1
[5] A='Dx'*(x-1)+x*y-y;
(y+Dx)*x-y-Dx
[6] function foo(x,y);
[7] B=foo(x,y)*x^2-1;
foo(x,y)*x^2-1

[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.3 引数

def sum(N) {
    for ( I = 1, S = 0; I <= N; I++ )
        S += I;
    return S;
}

これは, 1 から N までの自然数の和を求める函数 sum() の 定義である. この例における sum(N)N が引数である. この例は, 1 引数函数の例であるが, 一般に引数の個数は任意であり, 必要なだけの個数を ‘,’ で区切って指定することができる. 引数は 値が渡される. すなわち, 引数を受けとった側が, その引数の値を変更して も, 渡した側の変数は変化しない. ただし, 例外がある. それは, ベクトル, 行列を引数に渡した場合である. この場合も, 渡された変数そのものを書き 替えることは, その函数に局所的な操作であるが, 要素を書き換えた場合, それは, 呼び出し側のベクトル, 行列の要素を書き換えることになる.

def clear_vector(M) {
    /* M is expected to be a vector */
    L = size(M)[0];
    for ( I = 0; I < L; I++ )
        M[I] = 0;
}

この函数は, 引数のベクトルを 0 ベクトルに初期化するための函数である. また, ベクトルを引数に渡すことにより, 複数の結果を引数のベクトルに 収納して返すことができる. 実際には, このような場合には, 結果をリスト にして返すこともできる. 状況に応じて使いわけすることが望ましい.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.4 コメント

C と同様 ‘/*’ と ‘*/’ で囲まれた部分はコメントとして扱われる.

/*
 * This is a comment.
 */

def afo(X) {

コメントは複数行に渡っても構わないが, 入れ子にすることはできない. ‘/*’ がいくつあっても最初のもののみが有効となり, 最初に現れた ‘*/’ でコメントは終了したと見なされる. プログラムなどで, コメント を含む可能性がある部分をコメントアウトした場合には, #if 0, #endifを使えばよい. (See section プリプロセッサ.)

#if 0
def bfo(X) {
/* empty */
}
#endif

[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.5 文

Asir のユーザ函数は,

def 名前(引数,引数,...,引数) {
    文
    文
    ...
    文
}

という形で定義される. このように, 文は函数の基本的構成要素であり, プロ グラムを書くためには, 文がどのようなものであるか知らなければならない. 最も単純な文として, 単文がある. これは,

S = sum(N);

のように, 式に終端記号 (‘;’ または ‘$’) をつけたものである. この単文及び類似の return 文, break 文などが文の最小構成 単位となる. if 文や for 文の定義 (文法の詳細) を見れ ばわかる通り, それらの本体は, 単なる一つの文として定義されている. 通常 は, 本体には複数の文が書けることが必要となる. このような場合, ‘{’ と ‘}’ で文の並びを括って, 一つの文として扱うことがで きる. これを複文と呼ぶ.

if ( I == 0 ) {
    J = 1;
    K = 2;
    L = 3;
}

}’ の後ろには終端記号は必要ない. なぜなら, ‘{’ 文並び ‘}’が既に文となっていて, if 文の要請を満たしているからで ある.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.6 return

return 文は,

return 式;

return;

の 2 つの形式がある. いずれも函数から抜けるための文である. 前者は 函数の値として 式 を返す. 後者では, 函数の値として何が返されるか はわからない.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.7 if

if 文には

if ( 式 )             if ( 式 )
    文       及び         文
else
    文

の 2 種類がある. これらの動作は明らかであるが, 文の位置に if 文 が来た場合に注意を要する. 次の例を考えてみよう.

if ( 式 )
    if ( 式 ) 文
else
    文

この場合, 字下げからは, else 以下は, 最初の if に対応する ように見えるが, パーザは, 自動的に 2 番目の if に対応すると判断する. すなわち, 2 種類の if 文を許したために, 文法に曖昧性が現れ, それを 解消するために, else 以下は, 最も近い if に対応すると いう規則が適用されるのである. 従って, この例は,

if ( 式 ) {
    if ( 式 ) 文 else 文
}

という意味となる. 字下げに対応させるためには,

if ( 式 ) {
    if ( 式 ) 文
} else
    文

としなければならない.

関数の中でなく, top level で if 文を用いるときは $ または ; で終了する必要がある. これらがないと次の文がよみとばされる.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.8 ループ, break, return, continue

ループを構成する文は, while 文, for 文, do 文 の 3 種類がある.

ループを抜け出す手段として, break 文及び return 文がある. また, ループの制御を ある位置に移す手段として continue 文がある.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.9 構造体定義

構造体とは, 各成分の要素が名前でアクセスできる固定長配列と思ってよい. 各構造体は名前で区別される. 構造体は, struct 文により宣言される. 構造体が宣言されるとき, asir は内部で構造体のそれぞれの型に固有の識別 番号をつける. この番号は, 組み込み関数 struct_type により取得 できる. ある型の構造体は, 組み込み関数 newstruct により生成される. 構造体の各メンバは, 演算子 -> によりアクセスする. メンバが構造体の場合, -> による指定は入れ子にできる.

[1] struct rat {num,denom};
0
[2] A = newstruct(rat);
{0,0}
[3] A->num = 1;
1
[4] A->den = 2;
2
[5] A;
{1,2}
[6] struct_type(A);
1
参照

newstruct, struct_type


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.10 さまざまな式

主な式の構成要素としては, 次のようなものがある.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.11 プリプロセッサ

Asir のユーザ言語は C 言語を模したものである. C の特徴として, プリプロセッサ cpp によるマクロ展開, ファイルのインクルード があるが, Asir においてもユーザ言語ファイルの読み込みの際 cpp を通してから読み込むこととした. これによりユーザ言語 ファイル中で #include, #define, #if などが使える.

次の例は, ‘defs.h’ にあるマクロ定義である.

#define ZERO 0
#define NUM 1
#define POLY 2
#define RAT 3
#define LIST 4 
#define VECT 5
#define MAT 6
#define STR 7
#define N_Q 0
#define N_R 1
#define N_A 2
#define N_B 3
#define N_C 4
#define V_IND 0
#define V_UC 1
#define V_PF 2
#define V_SR 3
#define isnum(a) (type(a)==NUM)
#define ispoly(a) (type(a)==POLY)
#define israt(a) (type(a)==RAT)
#define islist(a) (type(a)==LIST)
#define isvect(a) (type(a)==VECT)
#define ismat(a) (type(a)==MAT)
#define isstr(a) (type(a)==STR)
#define FIRST(L) (car(L))
#define SECOND(L) (car(cdr(L)))
#define THIRD(L) (car(cdr(cdr(L))))
#define FOURTH(L) (car(cdr(cdr(cdr(L)))))
#define DEG(a) deg(a,var(a))
#define LCOEF(a) coef(a,deg(a,var(a)))
#define LTERM(a) coef(a,deg(a,var(a)))*var(a)^deg(a,var(a))
#define TT(a) car(car(a))
#define TS(a) car(cdr(car(a)))
#define MAX(a,b) ((a)>(b)?(a):(b))

C のプリプロセッサを流用しているため, プリプロセッサは $ を正しく処理できない. たとえば LIST が定義されていても LIST$は置換されない. $ の前に空白をおいて LIST $ と書かないといけない.


[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.12 オプション指定

ユーザ定義関数が N 変数で宣言された場合, その関数は, N 変数での呼び出しのみが許される.

[0] def factor(A) { return fctr(A); }
[1] factor(x^5-1,3);
evalf : argument mismatch in factor()
return to toplevel

不定個引数の関数をユーザ言語で記述したい場合, リスト, 配列を用いることで 可能となるが, 次のようなより分かりやすい方法も可能である.

% cat factor
def factor(F)
{
    Mod = getopt(mod);
    ModType = type(Mod);
    if ( ModType == 1 ) /* 'mod' is not specified. */   
        return fctr(F);
    else if ( ModType == 0 ) /* 'mod' is a number */
        return modfctr(F,Mod);
}  
[0] load("factor")$
[1] factor(x^5-1);
[[1,1],[x-1,1],[x^4+x^3+x^2+x+1,1]]
[2] factor(x^5-1|mod=11);
[[1,1],[x+6,1],[x+2,1],[x+10,1],[x+7,1],[x+8,1]]

2 番目の factor() の呼び出しにおいて, 関数定義の際に宣言された引 数 x^5-1の後ろに |mod=11 が置かれている. これは, 関数実行時 に, mod という keyword に対して 11 という値を割り当てること を指定している. これをオプション指定と呼ぶことにする. この値は getopt(mod) で取り出すことができる. 1 番目の呼び出しのように mod に対するオプション指定がない場合には, getopt(mod) は型 識別子 -1 のオブジェクトを返す. これにより, 指定がない場合の動作を if 文 により記述できる. ‘|’ の後ろには, 任意個のオプションを, ‘,’ で区切って指定することができる.

[100] xxx(1,2,x^2-1,[1,2,3]|proc=1,index=5);

さらに, オプションを key1=value1,key2=value2,... のように ‘,’ で区切って渡す代わりに, 特別なキーワード option_list とオプションリスト [["key1",value1],["key2",value2],...] を用いて渡すことも可能である.

[101] dp_gr_main([x^2+y^2-1,x*y-1]|option_list=[["v",[x,y]],["order",[[x,5,y,1]]]]); 

特に, 引数なしの getopt() はオプションリストを返すので, オプションをとる関数から, オプションをとる関数を呼び出すときには有用である.

% cat foo.rr
def foo(F)
{
    OPTS=getopt();
    return factor(F|option_list=OPTS);
}
[3] load("foo.rr")$
[4] foo(x^5-1|mod=11);
[[1,1],[x+6,1],[x+2,1],[x+10,1],[x+7,1],[x+8,1]]

[ << ] [ < ] [] [ > ] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

4.2.13 モジュール

ライブラリで定義されている関数, 変数をカプセル化する仕組みが モジュール (module) である. はじめにモジュールを用いたプログラムの例をあげよう.

module stack;

static Sp $
Sp = 0$
static Ssize$
Ssize = 100$
static Stack $
Stack = newvect(Ssize)$
localf push $
localf pop $

def push(A) {
  if (Sp >= Ssize) {print("Warning: Stack overflow\nDiscard the top"); pop();}
  Stack[Sp] = A;
  Sp++;
}
def pop() {
  local A;
  if (Sp <= 0) {print("Stack underflow"); return 0;}
  Sp--;
  A = Stack[Sp];
  return A;
}
endmodule;

def demo() {
  stack.push(1);
  stack.push(2);
  print(stack.pop());
  print(stack.pop());
}

モジュールは module モジュール名 〜 endmoduleで囲む. モジュールは入れ子にはできない. モジュールの中だけで使う大域変数は static で宣言する. この変数はモジュールの外からは参照もできないし変更もできない. static 変数はすべての関数定義の前に宣言しないといけない. パーサーがワンパスのため, 宣言のない変数は自動的に局所変数とみなされるからである. モジュールの外の大域変数は extern で宣言する.

モジュール内部で定義する関数は localf を用いて宣言しないといけない. 上の例では pushpop を宣言している. この宣言は必須である.

モジュール moduleName で定義された関数 functionName を モジュールの外から呼ぶには moduleName.functionName(引数1, 引数2, ... ) なる形式でよぶ. モジュールの中からは, 関数名のみでよい. 次の例では, モジュールの外からモジュール stack で定義された関数 push, pop を呼んでいる.

 stack.push(2);
 print( stack.pop() );
 2

モジュールで用いる関数名は局所的である. つまりモジュールの外や別のモジュールで定義されている関数名と同じ名前が 利用できる.

モジュール機能は大規模ライブラリの開発を想定している. ライブラリを必要に応じて分割ロードするには, 関数 module_definedp を用いるのが 便利である. デマンドロードはたとえば次のように行なえば良い.

if (!module_definedp("stack")) load("stack.rr") $

asir では局所変数の宣言は不要であった. しかしモジュール stack の例を見れば分かるように, local A; なる形式で 局所変数を宣言できる. キーワード local を用いると, 宣言機能が有効となる. 宣言機能を有効にすると, 宣言されてない変数はロードの段階で エラーを起こす. 変数名のタイプミスによる予期しないトラブルを防ぐには, 宣言機能を有効にしてプログラムするのがよい.

モジュール内の関数をそのモジュールが定義される前に 呼び出すような関数を書くときには, その関数の前でモジュールを次のように プロトタイプ宣言しておく必要がある.

/* Prototype declaration of the module stack */
module stack;
localf push $
localf pop $
endmodule;  

def demo() {
  print("----------------");
  stack.push(1);
  print(stack.pop());
  print("---------------");
}

module stack;
  /* The body of the module stack */
endmodule;

モジュールの中からトップレベルで定義されている関数を呼ぶには, 下の例のように :: を用いる.

def afo() {
  S = "afo, afo";
  return S;
}
module abc;
localf foo,afo $

def foo() {
  G = ::afo();
  return G;
}
def afo() {
  return "afo, afo in abc";
}
endmodule;
end$

[1200] abc.foo();
afo, afo
[1201] abc.afo();
afo, afo in abc
参照

module_list, module_definedp, remove_module.


[ << ] [ < ] [] [ > ] [ >> ]

この文書は4月 19, 2024texi2html 5.0を用いて生成されました。