4.2 ユーザ定義函数の書き方
4.2.1 ユーザ定義函数
ユーザによる函数の定義は ‘def’ 文で行う. 文法エラーは読み込み時に
ある程度チェックされ, おおよその場所が表示される.
既に(引数の個数に関係なく)同名の函数が定義されている場合には,
その函数は再定義される. ctrl()
函数により verbose
フラグ
が on になっている場合,
というメッセージが表示される. ある函数の定義において, まだ未定義の函数
を呼び出していても, 定義時にはエラーにならない. 実行時に未定義の函数
を呼び出そうとした場合にエラーとなる.
|
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 名前(引数,引数,...,引数) {
文
文
...
文
}
|
という形で定義される. このように, 文は函数の基本的構成要素であり, プロ
グラムを書くためには, 文がどのようなものであるか知らなければならない.
最も単純な文として, 単文がある. これは,
のように, 式に終端記号 (‘;’ または ‘$’) をつけたものである.
この単文及び類似の return
文, break
文などが文の最小構成
単位となる. if
文や for
文の定義 (文法の詳細) を見れ
ばわかる通り, それらの本体は, 単なる一つの文として定義されている. 通常
は, 本体には複数の文が書けることが必要となる. このような場合,
‘{’ と ‘}’ で文の並びを括って, 一つの文として扱うことがで
きる. これを複文と呼ぶ.
| if ( I == 0 ) {
J = 1;
K = 2;
L = 3;
}
|
‘}’ の後ろには終端記号は必要ない. なぜなら, ‘{’ 文並び
‘}’が既に文となっていて, if
文の要請を満たしているからで
ある.
4.2.6 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 種類がある.
-
while
文
形式は,
で, これは, 式 を評価して, その値が 0 でない限り 文 を実行するという
意味となる. たとえば 式 が 1 ならば, 単純な無限ループとなる.
-
for
文
形式は,
| for ( 式並び-1; 式; 式並び-2 ) 文
|
で, これは
| 式並び-1 (を単文並びにしたもの)
while ( 式 ) {
文
式並び-2 (を単文並びにしたもの)
}
|
と等価である.
-
do
文
は, 先に 文を実行してから条件式による判定を行う所が while
文
と異なっている.
ループを抜け出す手段として,
break
文及び return
文がある. また, ループの制御を
ある位置に移す手段として continue
文がある.
-
break
break
文は, それを囲むループを一つだけ抜ける.
-
return
return
文は, 一般に函数から抜けるための文であり,
ループの中からでも有効である.
-
continue
continue
文は, ループの本体の文の末端に制御を移す.
例えば for
文では, 最後の式並びの実行を行い, while
文では条件式の判定に移る.
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 さまざまな式
主な式の構成要素としては, 次のようなものがある.
-
加減乗除, 冪
冪は, ‘^’ により表す. 除算 ‘/’ は, 体としての演算に用いる.
例えば, 2/3
は有理数の 2/3
を表す.
整数除算, 多項式除算 (剰余を含む演算) には別途組み込み函数が用意されている.
-
インデックスつきの変数
ベクトル, 行列, リストの要素はインデックスを用いることにより取り出せる.
インデックスは 0 から始まることに注意する. 取り出した要素がベクトル,
行列, リストなら, さらにインデックスをつけることも有効である.
-
比較演算
等しい (‘==’), 等しくない (‘!=’), 大小 (‘>’, ‘<’,
‘>=’, ‘<=’) の 2 項演算がある. 真ならば有理数の 1, 偽ならば
0 を値に持つ.
-
論理式
論理積 (‘&&’), 論理和 (‘||’) の 2 項演算と, 否定 (‘!’)
が用意されている. 値はやはり 1, 0 である.
-
代入
通常の代入は ‘=’ で行う. このほか, 算術演算子と組み合わせて
特殊な代入を行うこともできる.
(‘+=’, ‘-=’, ‘*=’, ‘/=’, ‘^=’)
| 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, 値は変化後の値
|
4.2.11 プリプロセッサ
Asir のユーザ言語は C 言語を模したものである. C の特徴として,
プリプロセッサ cpp
によるマクロ展開, ファイルのインクルード
があるが, Asir においてもユーザ言語ファイルの読み込みの際
cpp
を通してから読み込むこととした. これによりユーザ言語
ファイル中で #include
, #define
, #if
などが使える.
-
#include
UNIX では インクルードファイルは, Asir のライブラリディレクトリ
(環境変数 ASIR_LIBDIR で指定されたディレクトリ)
と #include
が書かれているファイルと同じディレクトリをサーチする.
UNIX 以外では cpp
に特に引数を渡さないため,
#include
が書かれているファイルと同じディレクトリのみをサーチする.
-
#define
これは, C におけるのと全く同様に用いることができる.
-
#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 $
と書かないといけない.
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
を用いて宣言しないといけない.
上の例では 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
|
- 参照
module_list
, module_definedp
, remove_module
.
この文書はNobuki Takayamaによって2024年9月月6日にtexi2html 1.82を用いて生成されました。