関数と副プログラムは別物です。
Full BASICでは,関数の引数はすべて値渡しです。引数に変数を書いても,関数の副作用で変数値が変わってしまうことはありません。
一方,副プログラムと絵定義に変数を引数として書くと,参照渡しになります。これは,副プログラムまたは絵定義の実行中に引数の値を書き換えると,実引数の値が変わることを意味します。
Full BASICでは値が複数個の数値であるような関数の定義ができないので,その場合には,関数ではなく,副プログラムを使うことになります。
この違いは,見かけでは分からないので,初心者には落とし穴になります。つまり,関数定義の変数引数に参照渡しの動作を期待したり,あるいは,反対に,関数での経験から副プログラム中で引数の値を変更するプログラムを書いてしまい,副作用によるバグに悩むことがあります。
つまり,Full BASICでは,手続き定義で引数の値を変えるプログラムを書くことが許容されているのですが,特に必要な場合以外は,引数の値を変えるようなプログラムを書くのは避けておいたほうがいいといえます。
なお,副プログラムおよび絵定義で引数を値渡しにしたい場合は,変数名の前後を括弧でくくることで可能です。逆に,関数で変数引数を参照渡しにすることはできません。どうしてもという場合の対応は,引数とする代わりに外部関数定義の場合はモジュールの広域変数を用いるか,内部関数にする,関数をやめて副プログラムに書き換えるかです。
Full BASICでは配列を関数や副プログラムの引数にすることができます。
その場合,関数定義や副プログラムの内部で仮引数にした配列を重複してDIM文で宣言してはいけません。
Full BASICの手続き定義は,大きさ(要素数)の異なる配列を引数として受け取ることができます。
引数として受け取った配列の添字の下限と上限はLBOUND関数とUBOUND関数で調べられるので,配列の大きさを別に引数として渡す必要はありません。
関数定義の配列引数は値渡し,副プログラムと絵定義の配列引数は参照渡しになります。
Full BASICでは,配列値を取る関数を定義することができないので,結果として配列が欲しい場合は,副プログラムを利用することになります。
また,関数定義に大きな配列を渡すと配列の複写に時間がかかります。
Full BASICの宣言文の有効範囲は,プログラム単位です。
Full BASICの文法は,宣言文を内部手続き内に書くことを許していますが,その場合でも,その宣言文の有効範囲はプログラム単位です。DECLARE文やDIM文を内部手続き内に書いても内部手続きの局所変数の宣言にはなりません。
Full BASICでは,END行より手前を主プログラム,
END行以降にEXTERNALを前置して書かれる関数定義,副プログラム,絵定義を外部手続きといい,
主プログラムと外部手続きをプログラム単位といいます。
また,プログラム単位の内側に書かれる関数定義,副プログラム,絵定義を内部手続きといいます。
プログラム単位は変数名や宣言文の有効範囲です。
内部手続きの中では,仮引数以外の変数はプログラム単位で共有されます。
したがって,内部手続きからプログラム単位内の変数を参照し,書き換えることもできますが,内部手続き内での処理にその外側で使っているのと同じ名前の変数を使うと,論理の混乱を起こす可能性があることも意味します。
外部手続きは独立の存在なので,プログラムを記述するとき,他のプログラム単位で用いられている変数名について気を配る必要がありません。
その代わり,OPTION ANGLE,OPTION BASE などの宣言は,プログラム単位ごとに書かなければなりません。
OPTION BASE,OPTION ANGLEはプログラム単位ごとに異なってもかまわないので,忘れずに書く必要があります。
プログラム単位間で,引数によらずに変数を共有したい場合は,モジュールの機能を利用します。モジュールは,静的な変数と外部手続きの集まりです。モジュールでPUBLIC属性で宣言された変数は,モジュール外からも参照できます。
(静的な変数とは,プログラム実行中,常に存在する変数のことです。主プログラムの変数は静的ですが,外部手続き中の変数は,その手続きの実行を終えると消滅します。)
10 DECLARE EXTERNAL SUB solve 20 INPUT a,b,c 30 WHEN EXCEPTION IN 40 CALL solve(a,x,b,y,c) 50 PRINT x,y 60 USE 70 PRINT "解なし" 80 END WHEN 90 END 100 EXTERNAL SUB solve(a,x,b,y,c) 110 IF b=0 THEN 120 IF MOD(c,a)=0 THEN 130 LET x=c/a 140 LET y=0 150 ELSE 160 LET x=1/0 ! 例外を発生させる 170 END IF 180 ELSE 190 LET q=INT(a/b) 200 LET r=MOD(a,b) 210 CALL solve(b,u,r,v,c) 220 LET x=v 230 LET y=u-q*v 240 END IF 250 END SUB
このプログラムは,方程式 b u + r v = c の解を求めるために,210行のCALL文で副プログラムsolveの再帰呼び出しを行っています。
このプログラムを,そのまま単純に内部副プログラムに書き換えて,
100 INPUT a,b,c 110 WHEN EXCEPTION IN 120 CALL solve(a,x,b,y,c) 130 PRINT x,y 140 USE 150 PRINT "解なし" 160 END WHEN 170 SUB solve(a,x,b,y,c) 180 IF b=0 THEN 190 IF MOD(c,a)=0 THEN 200 LET x=c/a 210 LET y=0 220 ELSE 230 LET x=1/0 ! 例外を発生させる 240 END IF 250 ELSE 260 LET q=INT(a/b) 270 LET r=MOD(a,b) 280 CALL solve(b,u,r,v,c) 290 LET x=v 300 LET y=u-q*v 310 END IF 320 END SUB 330 END
とすると正しく動作しません。それは,260行で定めたqの値が,280行の副プログラム呼び出しによって書き換えられてしまい,300行でyの値を計算するときには260行で定めた値と異なるものになっているからです。
一方,外部副プログラムを用いるほうが正しく動作するのは,280行の実行中,参照引数以外のすべての変数について新たなメモリーを割り当てて計算が行われるため,手続き呼び出しが終わって戻ったときには,元の値が保存されているからです。(要するに,変数に対する実際の記憶場所が動的に変化するということです。)
<参照>拡張命令 LOCAL文