NScripterの深淵

NScripter/NSLua/Lua5.1/関連プログラム

▼メニュー

ゴブリンでもわかるNSLua/Lua5.1 第3回:Lua5.1入門 関数を呼び出す/ローカル変数

NScripterにおける「命令」

①引数=値を受け取る

②何らかの処理を実行する

③結果を返す

といった機能を有する。

Luaにおける関数(function)

①値を受け取る

②処理を実行する

③結果=値を返す

といった機能(function)を持つ。

NScripterと同じでしょ?(もちろん本当は色々異なるが今は省略する)

現時点では「とりあえず」命令とは関数である、関数とは命令であると理解してよい。

プログラミング用語としての「関数」

高校数学の初めの方で習ったであろう「f(x)=(x+1)2  y=f(x)」的な記述を思い出せるだろうか。この場合の「関数」は、

「①値を受け取り

 ②受け取った値を用いて何かしら計算し

 ③計算結果を返す

 箱のようなもの」

として説明されなかったか。

プログラミングの世界における「関数」も同じ概念だが、少しだけ柔軟性が高い。値を複数受け取ることもあれば、逆に1つも受け取らないこともある。値を複数返す事もあれば、逆に1つも返さないこともある。

Lua関数の呼び出し方

デフォルトで用意されている関数は色々ある(具体的には次々回あたりで紹介する:それまで我慢してね)が、例として「NSOkBox関数」を呼び出してみよう。

NSOkBox関数はNSLuaによって提供され、NScripterでいうmesbox命令に相当する機能を持つ。つまり、文字列を2つ受け取り、メッセージダイアログを表示する。

--◆「NSOkBox」という名前の変数を、関数として呼び出す

--引数に "タイトル" "本文" 2つの文字列を渡す
    NSOkBox("タイトル","本文")

--引数に aaa bbb 2つの変数の値(=中身)を渡す
    aaa,bbb="タイトル","本文"
    NSOkBox(aaa,bbb)

--引数に何も値を渡さない(エラー)
    NSOkBox()

値を返す関数

関数呼び出しは、代入式の右辺にも記述できる。

--「math.max」関数は2つの数値を受け取り、大きい方の値を返す
    aaa=math.max(10,20)
      -->aaa=20

--[[
math.maxの'.'が気になるかもしれない。
後々説明するので、今はそういう名前だと思っておいてほしい。
--]]

「関数から戻ってくる値/返される値」は、プログラミングにおいて「戻り値」または「返り値」と呼ばれる(return valueの直訳)。

実はNScripterにも同様の概念は存在するが、限定的にしか用いられない…プラグイン呼び出し後のgetret命令! NScripterでは、代わりに変数を直接参照・値を操作することで同様の操作を実現している。

--「%0という引数を受け取り、『%0+10』という値を返し、代入する関数」
add %0,10

関数は入れ子にできる。つまり、関数の引数に関数呼び出しを書いてもよい。

--数学と同じく手前・内側から処理される。
    aaa=math.max(10, math.max(20,30) )
      -->aaa=math.max(10,30)
      -->aaa=30
  
    aaa=math.max(
          --改行や注釈を挟んでもよい。
          10
          ,
          math.max(20,30)
        )

右辺に書ける、入れ子にもできる。おかげで色んな箇所の記述がスマートになるし、一時変数のようなハコも削減できる。

;◆NScripterの場合
;mesbox %0,"%0の中身です" ←本当はこう書きたいがエラーになる
itoa $0,%0
mesbox $0,"%0の中身です"

---------------------------------------------

--◆Luaの場合
--tostring関数は、受け取った値を文字列に変換して返す。
--NSOkBoxに文字列以外を渡すとエラーになるため、
--下記例では数値→文字列と変換している。
NSOkBox(tostring(10),tostring(20))

…言うほどスマートになっているだろうか?

関数を自分で定義する

NSOkBox(tostring(10),tostring(20))

毎回これでは面倒くさいので、以下のような関数があると嬉しい。 「呼び出される際に最大で2つの引数(title,text)を受け取る」 「受け取った引数を文字列に変換し、あらためてNSOkBox()を呼び出す」

alert(10,20)

さっそく作ってみよう。「alertという名前の変数に、関数を代入する」と、alert関数を定義できる。

--[[
◆関数の定義
 変数名=function(受け取る引数名リスト)
             ~処理内容。コードを自由に記述できる~
         end
--]]
alert=function(title,text)
          NSOkBox(tostring(title),tostring(text))
      end

--[[
◆関数の呼び出し
 変数「alert」を関数として呼び出す。
 引数には「10」「20」を渡す
--]]
alert(10,20)

--コード例を簡略化するために、
--ここ以降ではalert関数が定義されているものとする。

「変数に関数を代入する」という言い回しは奇妙に聞こえるかもしれない。

「変数に数値を代入する」 「変数に文字列を代入する」 「変数に関数を代入する」 こう並べてみてほしい。

NScripterの変数は、「文字列型」または「数値型」の2種類のみだった。自作命令の定義には特別な命令であるdefsubを用いる必要があり、定義可能な場所も定義節に限られる。

Luaの変数は、値の一種として「関数」も扱う。数値や文字列と同じく自由に関数を定義・代入できるし、どこからでも上書きできる。

--定義
test=function(str)
         return str.."だよ"
     end
alert( test("テスト") ) -->"テストだよ"

--別の変数に代入
test2=test
alert( test2("実験") ) -->"実験だよ"

--上書き
test=function(str)
         return str.."ですよ"
     end
alert( test("テスト") ) -->"テストですよ"

色々な関数定義の例

(処理内容自体に実用性は無い)

--◆戻り値を返す関数
test=function(num)
        --数値を受け取り、2倍した値を返す
        return num*2
     end
a=test(100) -->a=200


--◆関数内部にあれこれコードを書く
test=function(num)
        --数値を受け取り、正なら2倍、負なら-10倍する関数
        if(num>0)then
            return num*2
        else
            return num*-10
        end
     end
a=test(100) -->a=200
a=test(-10) -->a=100


--◆複数の引数、複数の戻り値
test2=function(num,num2)
        --2つの数値を受け取り、2つの数値を返す関数
        return num+1,num2+2
      end
a,b=test2(100,200) -->a,b=101,202

ところで:ローカル変数

自作関数で引数を受け取るために使った変数名(ここまでの例ではtitle,text,num,num2など)は、「関数内部でのみ利用可能な変数」となる。

test=function(aaa)
         ----
     end
alert(aaa)-->aaaは未定義

こうした変数(一部のコードからのみ見えている変数)をローカル変数と呼ぶ。下記例の通り、たとえ関数の外部で同名の変数が定義されていたとしても、ローカル変数が定義されていた場合はそちらが優先される。

aaa=3
alert(aaa)-->3

test=function(aaa)
        alert(aaa)
     end
test(10)-->10

実はforの変数もローカル変数である。

for i=1,10 do
    alert(i)-->i=1,2,3,4,5,...,10
end
alert(i)-->iは未定義

ローカル変数はそれ以外の場所でも定義できる。

if(1==1)then
    --ifの中でのみ有効
    local aaa    
    aaa=10
end

if(1==1)then
    --入れ子になっている場合、
    --コード位置より上の階層にあるローカル変数のみ有効
    local aaa
    aaa=10
    if(1==1)then
        local aaa    
        aaa=20
        alert(aaa)-->20
    end
    alert(aaa)-->10
end
alert(aaa)-->aaaは未定義


for i=1,10 do
    --forの中でのみ有効、初期値は100
    local bbb=100
end


test=function()
        --この関数内部でのみ有効、初期値は"あいう","えお"
        local ccc,ddd="あいう","えお"
     end

test=function(num)
        --ローカル変数に関数を代入してもよい
        local fff=function(aaa)
                    return aaa*2
              end
        
        return fff(num)
        
     end

こうした「ローカル変数の有効範囲」の事をブロック/スコープと読んだりするが、名前は忘れていい。

    --「do」は「if(1==1)then」等と等しい
    --(ブロックをなし、判定なしに内部を実行する)
    do
        local aaa=1
    end

    if(1==1)then
        local aaa=1
    end

ローカル変数の何が便利か。

*define
numalias temp1,%0 :inc %0
numalias temp2,%0 :inc %0
numalias temp3,%0 :inc %0
numalias temp4,%0 :inc %0

        ......

;defsub命令または汎用サブルーチン
*mygosub
    mov %temp1,...
    mov %temp2,...
return

*mygosub2
    mov %temp1,...
    mov %temp2,...
return    

あなたが一定程度面倒臭がりで、しかも手元にあるNScripterスクリプトが一定以上複雑だったなら、きっとこの手の汎用変数が用意されているはずだ。

スクリプトの特定箇所でしか使わない変数名をいちいち定義節に戻って書くのってめんどくね?」と思った事はないだろうか。

gosub先から多重にgosubする際、使う汎用変数がうっかり被ったりしないか気を回した事などもあるかもしれない。

ローカル変数を使えばそうしたアレコレが一切必要なくなる。その場でしか使わない変数はその場で定義する!

ここまででNScripter命令に相当する関数の呼び出しと定義、便利なローカル変数について知った事になる。それで関数にどんなもんが用意されてるのさ?と気になる気持ちは理解しつつ、次回は少し寄り道をして超便利な配列…連想配列について説明する。

local tab={}
tab[1]=2
tab["hoge"]=4

余談:

どこからでも参照・利用できる変数(今まで使っていたヤツら)は、Luaにおいて「グローバル変数」と呼ばれる。NScripterにおける通常変数は、このLuaの定義を当てはめた場合「グローバル変数」であることになる。名前被って紛らわしいね。

「その場で使いきりの変数」には基本的にローカル変数が用いられると認識してよい。だからこれまで書いてきたグローバル変数だらけのコードはほとんど嘘っぱちなんです。