[ << ] | [ < ] | [上] | [ > ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
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
[ << ] | [ < ] | [上] | [ > ] | [ >> ] |
この文書は12月 21, 2024にtexi2html 5.0を用いて生成されました。