| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
| 4.2.1 ユーザ定義函数 | ||
| 4.2.2 変数および不定元 | ||
| 4.2.3 引数 | ||
| 4.2.4 コメント | ||
| 4.2.5 文 | ||
4.2.6 return 文 | ||
4.2.7 if 文 | ||
4.2.8 ループ, break, return, continue | ||
| 4.2.9 構造体定義 | ||
| 4.2.10 さまざまな式 | ||
| 4.2.11 プリプロセッサ | ||
| 4.2.12 オプション指定 | ||
| 4.2.13 モジュール |
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
ユーザによる函数の定義は ‘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 言語 によるプログラムの書き方を解説する.
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
既に述べた通り, 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
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
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 ベクトルに初期化するための函数である. また, ベクトルを引数に渡すことにより, 複数の結果を引数のベクトルに 収納して返すことができる. 実際には, このような場合には, 結果をリスト にして返すこともできる. 状況に応じて使いわけすることが望ましい.
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
C と同様 ‘/*’ と ‘*/’ で囲まれた部分はコメントとして扱われる.
/*
* This is a comment.
*/
def afo(X) {
コメントは複数行に渡っても構わないが, 入れ子にすることはできない.
‘/*’ がいくつあっても最初のもののみが有効となり, 最初に現れた
‘*/’ でコメントは終了したと見なされる. プログラムなどで, コメント
を含む可能性がある部分をコメントアウトした場合には, #if 0,
#endifを使えばよい. (See section プリプロセッサ.)
#if 0
def bfo(X) {
/* empty */
}
#endif
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
Asir のユーザ函数は,
def 名前(引数,引数,...,引数) {
文
文
...
文
}
という形で定義される. このように, 文は函数の基本的構成要素であり, プロ グラムを書くためには, 文がどのようなものであるか知らなければならない. 最も単純な文として, 単文がある. これは,
S = sum(N);
のように, 式に終端記号 (‘;’ または ‘$’) をつけたものである.
この単文及び類似の return 文, break 文などが文の最小構成
単位となる. if 文や for 文の定義 (文法の詳細) を見れ
ばわかる通り, それらの本体は, 単なる一つの文として定義されている. 通常
は, 本体には複数の文が書けることが必要となる. このような場合,
‘{’ と ‘}’ で文の並びを括って, 一つの文として扱うことがで
きる. これを複文と呼ぶ.
if ( I == 0 ) {
J = 1;
K = 2;
L = 3;
}
‘}’ の後ろには終端記号は必要ない. なぜなら, ‘{’ 文並び
‘}’が既に文となっていて, if 文の要請を満たしているからで
ある.
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
return 文return 文は,
return 式; return;
の 2 つの形式がある. いずれも函数から抜けるための文である. 前者は 函数の値として 式 を返す. 後者では, 函数の値として何が返されるか はわからない.
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
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 文を用いるときは $ または ;
で終了する必要がある.
これらがないと次の文がよみとばされる.
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
break, return, continueループを構成する文は, while 文, for 文, do 文
の 3 種類がある.
while 文
while ( 式 ) 文
で, これは, 式 を評価して, その値が 0 でない限り 文 を実行するという 意味となる. たとえば 式 が 1 ならば, 単純な無限ループとなる.
for 文
for ( 式並び-1; 式; 式並び-2 ) 文
で, これは
式並び-1 (を単文並びにしたもの)
while ( 式 ) {
文
式並び-2 (を単文並びにしたもの)
}
と等価である.
do 文
do {
文
} while ( 式 )
は, 先に 文を実行してから条件式による判定を行う所が while 文
と異なっている.
ループを抜け出す手段として,
break 文及び return 文がある. また, ループの制御を
ある位置に移す手段として continue 文がある.
break
break 文は, それを囲むループを一つだけ抜ける.
return
return 文は, 一般に函数から抜けるための文であり,
ループの中からでも有効である.
continue
continue 文は, ループの本体の文の末端に制御を移す.
例えば for 文では, 最後の式並びの実行を行い, while
文では条件式の判定に移る.
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
構造体とは, 各成分の要素が名前でアクセスできる固定長配列と思ってよい.
各構造体は名前で区別される. 構造体は, 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
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
主な式の構成要素としては, 次のようなものがある.
2/3 は有理数の 2/3 を表す.
整数除算, 多項式除算 (剰余を含む演算) には別途組み込み函数が用意されている.
x+1 A^2*B*afo X/3
V[0] M[1][2]
A = 2 A *= 3 (これは A = A*3 と同じ; その他の演算子も同様)
A++ 値は元の A の値, A = A+1 A-- 値は元の A の値, A = A-1 ++A A = A+1, 値は変化後の値 --A A = A-1, 値は変化後の値
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
Asir のユーザ言語は C 言語を模したものである. C の特徴として,
プリプロセッサ cpp によるマクロ展開, ファイルのインクルード
があるが, Asir においてもユーザ言語ファイルの読み込みの際
cpp を通してから読み込むこととした. これによりユーザ言語
ファイル中で #include, #define, #if などが使える.
#include
#include が書かれているファイルと同じディレクトリをサーチする.
UNIX 以外では cpp に特に引数を渡さないため,
#include が書かれているファイルと同じディレクトリのみをサーチする.
#define
#if
/*, */ によるコメントは入れ子にできないので, プログラム
の大きな部分をコメントアウトする際に, #if 0, #endif
を使うと便利である.
次の例は, ‘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 $ と書かないといけない.
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
ユーザ定義関数が 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]]
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
ライブラリで定義されている関数, 変数をカプセル化する仕組みが モジュール (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 を用いて宣言しないといけない.
上の例では push と pop を宣言している.
この宣言は必須である.
モジュール 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
| [ << ] | [ < ] | [上] | [ > ] | [ >> ] |
この文書は11月 5, 2025にtexi2html 5.0を用いて生成されました。