目次
関数の概念
関数とは
プログラミングにおける関数とは、与えられた値をもとに決まった処理(計算など)を行いその結果を返す、またはそれらの命令のまとまりのことです。
UWSCにおいて関数には、標準関数(組み込み関数)とユーザー定義関数の2種類があります。
標準関数とはUWSCが最初から用意している関数のことで、プログラマが定義をしなくても使うことができるものです。UWSCではGETID関数、GETTIME関数、FOPEN関数などが標準関数と呼ばれるものです。
標準関数は他にもたくさんあり、スクリプト関数にUWSCが定義した関数を載せています。
一方、ユーザー定義関数とは標準関数にない機能を独自に作成した関数のことで、プログラマが関数名や行う処理などを自由に定義することができます。
戻り値があるときとないときで定義の仕方が異なり、以下のどちらかで関数を定義します。
- PROCEDURE-FEND
- 戻り値のない自作関数を定義する。
- FUNCTION-FEND
- 戻り値のある自作関数を定義する。
自作関数に当サイトで使っているユーザー定義関数を載せています。
関数を構成する要素
関数宣言
関数宣言とは、プログラムのまとまりに名前(関数名)を付け関数を定義することです。戻り値がない場合はPROCEDURE、ある場合はFUNCTIONを記述します。
PROCEDURE、FUNCTIONの次の行から処理を記述していき、最後はFENDで関数の定義を終了します。関数の定義開始位置からFENDの手前までが定義された関数が行う処理となります。
// 戻り値なし
PROCEDURE 関数名()
// 処理
FEND
// 戻り値あり
FUNCTION 関数名()
// 処理
RESULT = 戻り値
FEND
関数名
関数宣言(PROCEDUREまたはFUNCTION)の直後に半角スペースを入れ関数名を定義します。関数名のすぐ後ろには()を記述します。引数がない場合は括弧の記述がなくても一応動作はしますが、括弧ありで統一しておく方が良いと思います。
引数
関数へ渡された情報を受け取るための変数を記述します。変数名は呼び出した側の変数と同じである必要はなく、関数内でわかりやすい名前を付けます。引数が複数ある場合は,(カンマ)で区切ります。引数が一つもない場合は( )内には何も記述する必要はありません。
戻り値
FUNCTIONで関数宣言を行った場合は戻り値を返す必要があり、戻り値を返すには特殊変数のRESULTを使います。
FUNCTION func()
RESULT = 戻り値
FEND
C言語やPHPのreturn文のように戻り値を代入した時点で関数抜けるわけではないので注意が必要です。以下の例ではより後に代入されるFalseがfunc関数の戻り値として返ります。
PRINT func()
FUNCTION func()
RESULT = TRUE
RESULT = FALSE
FEND
- 結果
False
オーバーライド
オーバーライドとは、標準関数と同名の関数を再定義することで関数の内容を上書きすることです。
以下にREPLACE関数 (スクリプト関数)を書き換える処理を例として示します。通常REPLACE関数 (スクリプト関数)は第三引数までしか指定できないので、第四引数を指定しても無視されすべての生がゆでに置換されます。
PRINT REPLACE("生麦生米生卵", "生", "ゆで", 3)
- 結果
ゆで麦ゆで米ゆで卵
以下はUWSCの標準関数にあるREPLACE関数 (スクリプト関数)には存在しない第四引数に指定された位置の文字列のみ置換する関数に書き換えています。そのため同じ処理を実行しても以下は3番目に見つかった生だけ書き換えることができる関数に書き換えることができます。
PRINT REPLACE("生麦生米生卵", "生", "ゆで", 3)
FUNCTION REPLACE(string, search, replace, occurrence = 1)
RESULT = substrReplace(string, replace, POS(search, string, occurrence), LENGTH(search))
FEND
FUNCTION substrReplace(string, replace, offset, length = EMPTY)
IF offset < 0 THEN offset = LENGTH(string) + offset + 1
SELECT TRUE
CASE length = EMPTY
RESULT = COPY(string, 1, offset - 1) + replace
CASE length >= 0
RESULT = COPY(string, 1, offset - 1) + replace + COPY(string, offset + length)
CASE length < 0
RESULT = COPY(string, 1, offset - 1) + replace + COPY(string, LENGTH(string) + length + 2)
SELEND
FEND
- 結果
生麦生米ゆで卵
関数を定義するメリット
関数を定義するメリットを以下に示しておきます。
- 再利用性
- 関数を定義することで必要な箇所で関数名を指定するだけで一連の処理を呼び出すことができるようになり、重複した処理を何度も書く必要がなくなります。
- 可読性の向上
- 関数名に適切な名前を付けておくことで、その関数がどんな処理を行っているのかが理解しやすくなります。例えばtaxPriceという関数を定義した場合、税抜価格から税込価格を求める関数だというのが関数名からわかると思います。
以下はInternetExplorer オブジェクトを生成し指定したURLのページをいくつか読み込むプログラムです。
DIM IE = CREATEOLEOBJ("InternetExplorer.Application")
IE.Visible = TRUE
IE.Navigate("https://example.com")
REPEAT
SLEEP(0.001)
UNTIL !IE.Busy AND IE.readyState = 4
IE.Navigate("https://google.com")
REPEAT
SLEEP(0.001)
UNTIL !IE.Busy AND IE.readyState = 4
IE.Navigate("https://yahoo.co.jp")
REPEAT
SLEEP(0.001)
UNTIL !IE.Busy AND IE.readyState = 4
IE.Navigate メソッドの直後にあるREPEAT文から始まる3行は、Webページの読み込みの完了を待機するためのプログラムです。このようにプログラム内で何度も使う処理は再利用性、可読性どちらの点からも関数化した方が良いです。以下は待機部分をBusyWaitという名前の関数でまとめたプログラムです。
以下のようにするとBusyWait(IE)と書くだけでWebページの読み込みを待機することができ、また関数の修正が必要になったときに定義した関数内だけ修正すれば良いので保守性も向上します。
DIM IE = CREATEOLEOBJ("InternetExplorer.Application")
IE.Visible = TRUE
IE.Navigate("https://example.com")
BusyWait(IE)
IE.Navigate("https://google.com")
BusyWait(IE)
IE.Navigate("https://yahoo.co.jp")
BusyWait(IE)
PROCEDURE BusyWait(IE)
REPEAT
SLEEP(0.001)
UNTIL !IE.Busy AND IE.readyState = 4
FEND
ブラックボックス
関数は与えられた引数によって結果を返します。そのため関数の内部構造や動作を知らなくても関数名と入出力の関係(引数と戻り値)だけ理解していれば良いのでブラックボックスとして扱うことができます。
以下の例だとcircleArea関数に引数として半径を渡せば円の面積が返ってくることがわかっていれば、円の面積が半径の2乗×円周率で求めるということは理解していなくても利用できることになります。
PRINT circleArea(5)
FUNCTION circleArea(r)
RESULT = POWER(r, 2) * 3.1415926535
FEND
これは標準関数についても同じです。例えばGETID関数は引数に指定した文字列を含むウィンドウのIDを取得する関数ですが、この関数がどのようにしてIDを取得しているのかは理解していなくても関数名と引数に何を指定すれば良いのか把握していれば使いこなすことができます。
関数の作り方
関数は引数・戻り値の有無で4種類のパターンに分けられます。戻り値がない場合はPROCEDURE-FEND、戻り値がある場合はFUNCTION-FENDで定義します。パターン毎に関数の例を示しておきます。
引数なし・戻り値なし
引数によって処理内容を変える必要がなく、常に決まった処理を実行するときに使います。
PROCEDURE 関数名()
処理
FEND
- forceQuit関数(自作関数)
- 呼び出された時点で処理を強制終了します。引数・戻り値ともにありません。
引数なし・戻り値あり
結果に変化があるときに使います。
FUNCTION 関数名()
処理
RESULT = 戻り値
FEND
引数あり・戻り値なし
違うデータを渡して処理を調整したいときに使います。
PROCEDURE 関数名(引数)
処理
FEND
- CTRLWIN関数
- 引数に指定したウィンドウIDを操作します。
- FUKIDASI関数
- 引数に指定したメッセージを指定位置に表示します。
- SLEEP関数
- 引数に指定した時間(秒)待機します。
引数あり・戻り値あり
データを渡して計算を行い、その結果を取得したいときに使います。
FUNCTION 関数名(引数)
処理
RESULT = 戻り値
FEND
引数
引数とは、関数やメソッドの呼び出し元と呼び出し先で値をやり取りするための変数やその値のことを指します。関数名のあとに括弧( )を書き、括弧内に引数を記述します。
PROCEDURE 関数名(仮引数)
引数が複数ある場合は、カンマ区切りで必要な数だけ記述していきます。
PROCEDURE 関数名(仮引数1, 仮引数2, 仮引数3, …)
仮引数・実引数
関数を定義する際に外部から値を渡される特別な変数として指定されるのが仮引数、関数を呼び出す式において仮引数に対応する式もしくは値が実引数です。プログラム実行時には仮引数が実引数の値を受け取ります。
仮引数
仮引数とは関数で定義される変数のうち、実行時に呼び出し元から渡される値を受けるものをいいます。
以下に示すsum関数では、xとyが仮引数となります。
FUNCTION sum(x, y)
RESULT = x + y
FEND
実引数
実引数とはその関数を呼び出す際に渡す値のことで、関数の動作や結果に影響を与えます。変数や値の他に式を渡すこともできます。
以下に示すプログラムではsum関数に渡すaとbが実引数となります。
DIM a = 1
DIM b = 2
PRINT sum(a, b)
FUNCTION sum(x, y)
RESULT = x + y
FEND
引数の渡し方
関数やメソッドへの引数の渡し方には値渡しと参照渡しの2種類の方法があります。
値渡し
値渡しとは変数の値をコピーして渡す方法のことです。関数の呼び出し元の引数である実引数の値を呼び出された関数の仮引数にコピーして渡すので、仮引数の値を変更しても呼び出し元の実引数の変数には影響しません。
参照渡し
参照渡しとは変数のメモリ番地を渡す方法のことです。関数の呼び出し元の実引数と呼び出された関数の仮引数が同じ実体を表すようになります。呼び出された関数の仮引数を変更すると、呼び出し元の実引数の変数も変更されます。引数を参照渡しにするには呼び出された関数の仮引数の前にVarを付けます。
デフォルト引数
デフォルト引数とは、引数に初期値を設定する機能のことです。関数を呼び出すときに、引数を指定していればその値が使用され、引数が省略された場合はデフォルト値が代わりに使用されます。デフォルトパラメータ以降に通常引数を書くことはできません。
例えば、GETID関数 (スクリプト関数)を自分で書くとすれば以下のような感じになります。
GETID( タイトル, [クラス名, 待ち時間秒, MDI子タイトル] )
FUNCTION GETID(title, class = EMPTY, waitTime = 0, mdiTitle = EMPTY)
処理
FEND
タイトルは必須ですが、クラス名・待ち時間秒・MDI子タイトルは必須ではないため省略することができます。待ち時間秒はデフォルトの値が0なのでwaitTime = 0となります。これで値が指定されたときはその値、省略されたときは0が代入されます。
デフォルト引数にEmptyを指定する場合=の後ろを省略することもできます。
以下のように書くことでclassとmdiTitleは省略された場合、仮引数にはEmptyが代入されます。
FUNCTION GETID(title, class = , waitTime = 0, mdiTitle = )
処理
FEND
戻り値
戻り値とはプログラム中に呼び出された関数やメソッドなどが処理を行った結果として呼び出し元に対して返す値のことで、FUNCTION-FENDで関数を定義します。計算結果の数値や処理したデータや、処理が正しく終了したかを表す真偽値を返すときに使います。
以下はsum関数にaとbを引数として渡し、その和を返します。sum関数の引数として渡したa、bはsum関数内で仮引数x、yにそれぞれ代入されます。sum関数内のRESULTに指定した値が戻り値となるので仮引数のxとyの和である3(=1+2)が戻り値で呼び出し元にsum関数の結果として返され、cに3が代入されます。
DIM a = 1
DIM b = 2
DIM c = sum(a, b)
FUNCTION sum(x, y)
RESULT = x + y
FEND
FUNCTION-FENDで定義した関数で戻り値を指定していても、必要がなければ呼び出し元でその値を受け取る必要はありません。
例えばarrayPush関数 (自作関数)の場合、第一引数のarrayに指定した配列の末尾に第二引数以降の値を追加し追加後の要素数を戻り値として返します。
以下の場合、空の配列arrayにaを追加したときは要素数が1で戻り値として返しその値を出力していますが、さらにbを追加したときの要素数を戻り値として2を返しますが出力はしていません。
このようにarrayPush関数 (自作関数)で要素数を返していても必要がなければ無視してもエラーにはなりません。
DIM array[-1]
PRINT arrayPush(array, "a")
arrayPush(array, "b")
//////////////////////////////////////////////////
// 【引数】
// array : 要素を追加する配列(参照引数)
// values : 追加する要素をvalue1から指定
// 【戻り値】
// 処理後の配列の要素の数
//////////////////////////////////////////////////
FUNCTION arrayPush(var array[], value1 = EMPTY, value2 = EMPTY, value3 = EMPTY, value4 = EMPTY, value5 = EMPTY, value6 = EMPTY, value7 = EMPTY, value8 = EMPTY, value9 = EMPTY, value10 = EMPTY, value11 = EMPTY, value12 = EMPTY, value13 = EMPTY, value14 = EMPTY, value15 = EMPTY, value16 = EMPTY)
DIM i = 1
WHILE EVAL("value" + i) <> EMPTY
DIM res = RESIZE(array, UBound(array) + 1)
array[res] = EVAL("value" + i)
i = i + 1
WEND
RESULT = LENGTH(array)
FEND
//////////////////////////////////////////////////
// 【引数】
// inputs : 繰り返す文字列
// multiplier : inputsを繰り返す回数
// 【戻り値】
// inputsをmultiplier回を繰り返した文字列を返します
//////////////////////////////////////////////////
FUNCTION strRepeat(inputs, multiplier)
DIM res = ""
FOR n = 1 TO multiplier
res = res + inputs
NEXT
RESULT = res
FEND
//////////////////////////////////////////////////
// 【引数】
// arrayname : 上限値を求める配列の名前
// dimension : 返す次元を示す整数
// 【戻り値】
// 配列の上限値
//////////////////////////////////////////////////
FUNCTION UBound(arrayname[], dimension = 1)
RESULT = EVAL("RESIZE(arrayname" + strRepeat("[0]", dimension - 1) + ")")
FEND
- 結果
1
一次元配列
戻り値は基本的に一つの値を返すために使いますが、RESULTを配列にすることで複数の値を返すこともできます。
SAFEARRAY関数 (スクリプト関数)を使うことで一次元配列を戻り値にすることができ、RESULTにSAFEARRAY(下限値, 上限値)を指定しその配列に値を格納していくことで一次元配列を返すことができます。
以下はRESULTの下限値に0、上限値に2を指定する例です。まず配列のサイズを指定し、各要素に値を代入していきます。戻り値が配列なのでFOR-IN文で各要素にアクセスすることができます。
FOR item IN func()
PRINT item
NEXT
FUNCTION func()
RESULT = SAFEARRAY(0, 2)
RESULT[0] = 0
RESULT[1] = 1
RESULT[2] = 2
FEND
- 結果
0 1 2
RESULT以外で配列変数を用意している場合、SLICE関数 (スクリプト関数)を使うことで一次元配列を戻り値にすることもできます。
以下は配列変数arrayに格納された値ををRESULTに戻り値として返すときの書き方です。
FOR item IN func()
PRINT item
NEXT
FUNCTION func()
DIM array[2] = 0, 1, 2
RESULT = SLICE(array)
FEND
- 結果
0 1 2
二次元配列
RESULTにSAFEARRAY(一次元下限, 一次元上限, 二次元下限, 二次元上限)を代入することで二次元配列を返すこともできます。
UBound関数 (自作関数)で各次元の上限値を求め、二次元配列の各要素にアクセスしていきます。
DIM array = func()
FOR i = 0 TO UBound(array)
FOR j = 0 TO UBound(array, 2)
PRINT "[" + i + "][" + j + "] " + array[i][j]
NEXT
NEXT
FUNCTION func()
RESULT = SAFEARRAY(0, 1, 0, 2)
RESULT[0][0] = 0
RESULT[0][1] = 1
RESULT[0][2] = 2
RESULT[1][0] = 3
RESULT[1][1] = 4
RESULT[1][2] = 5
FEND
//////////////////////////////////////////////////
// 【引数】
// inputs : 繰り返す文字列
// multiplier : inputsを繰り返す回数
// 【戻り値】
// inputsをmultiplier回を繰り返した文字列を返します
//////////////////////////////////////////////////
FUNCTION strRepeat(inputs, multiplier)
DIM res = ""
FOR n = 1 TO multiplier
res = res + inputs
NEXT
RESULT = res
FEND
//////////////////////////////////////////////////
// 【引数】
// arrayname : 上限値を求める配列の名前
// dimension : 返す次元を示す整数
// 【戻り値】
// 配列の上限値
//////////////////////////////////////////////////
FUNCTION UBound(arrayname[], dimension = 1)
RESULT = EVAL("RESIZE(arrayname" + strRepeat("[0]", dimension - 1) + ")")
FEND
- 結果
[0][0] 0 [0][1] 1 [0][2] 2 [1][0] 3 [1][1] 4 [1][2] 5
関数を定義する位置
自作関数はメインルーチンの最後に記述します。以下のように途中に記述するとabcは出力されますが、123は出力されません。
PRINT "abc"
PROCEDURE func()
…処理…
FEND
PRINT "123"
以下のように記述するとabcも123も出力されます。
PRINT "abc"
PRINT "123"
PROCEDURE func()
…処理…
FEND
関数の呼び出し方
関数は定義しただけでは実行されず、呼び出すことで使うことができます。関数を実行するには以下のように記述します。
関数名()
外部に定義した関数を実行するには、CALL文で使用する関数を含むファイルを呼び出してから関数名()と記述します。CALL文で呼び出すファイルは 絶対パス でも 相対パス どちらで指定しても大丈夫です。
以下はD:\Programs\UWSC\FUNCTIONS.uwsに定義したtax関数を呼び出す例。
CALL D:\Programs\UWSC\FUNCTIONS.uws
PRINT tax()
定義されていない関数を呼び出そうとすると、関数: (関数名) がありませんとエラーが表示されます。