1. ホーム
  2. スクリプト・コラム
  3. ルア

Luaチュートリアル(IV)。関数の詳細

2022-02-13 04:05:04

I. 機能

    Luaの関数は、print("Hello World")やa = add(x, y)など、基本的にC言語と同じように呼び出されます。ただ、print "Hello World"とf {x = 20, y = 20}のように、関数の引数が1つだけで、引数の型が文字列定数やテーブルのコンストラクタの場合はカッコを省略することができる点が異なる。
    また、Luaにはオブジェクト指向の呼び出しのための特別な構文として、コロン演算子が用意されています。o.foo(o,x)という式は、o:foo(x)と書くこともできます。コロン演算子によって、o.fooの呼び出しは暗黙のうちにoを関数の第1引数として受け取ります。
    Luaでは、関数は次のように宣言されます。

コピーコード コードは以下の通りです。

    function add(a)
        local sum = 0
        for i, v in ipairs(a) do
            sum = sum + v
        end
        return sum
    end

    上記の宣言では、関数名(add)、引数リスト(a)、関数本体が含まれています。注意すべきは、Luaでは実引数と仮引数の数が一致しないことがあることです。これが発生すると、Luaの処理規則は多重代入と同等になります。つまり、実パラメータが形式パラメータより多い場合、超過分は無視され、逆の場合は初期化されていない形式パラメータのデフォルト値はnilになります。

1. 複数の戻り値。

    Luaは複数の結果値を返すことをサポートしています。例えば

コピーコード コードは以下の通りです。

s,e = string.find("Hello Lua users","Lua")
print("The begin index is " . s ... ", the end index is " . e ... ". ");
-- The begin index is 7, the end index is 9.

    The above code example just shows how to get multiple return values for a Lua function. The following example will give you how to declare a Lua function that returns multiple values. For example.
[code]
function maximum(a)
    local mi = 1
    local m = a[mi]
    for i, val in ipairs(a) do
        if val > m then
            mi,m = i,val
        end
    end
    return m,mi
end
print(maximum{8,10,23,12,5})
--23 3

Luaは、様々な呼び出しのケースに合わせて、関数の戻り値の数を調整します。関数が単一のステートメントとして呼び出された場合、Luaはその関数の戻り値を全て破棄します。関数が式の一部として呼び出された場合、Luaはその関数の最初の戻り値のみを保持します。関数呼び出しが一連の式の最後の要素である場合のみ、全ての戻り値を利用することができます。ここではまず、次のような3つのサンプル関数が与えられています。

コピーコード コードは以下の通りです。

    function foo0() end
    function foo1() return "a" end
    function foo2() return "a","b" end

 最後に紹介するのは、Luaのunpack関数で、配列を引数にとり、その配列のすべての要素を添え字1から順に返してくれる。例えば、以下のようになります。

コピーコード コードは以下の通りです。

    /> lua
    > print(unpack{10,20,30})
    10 20 30
    > a,b = unpack{10,20,30}
    > print(a,b)
    10 20
    > string.find(unpack{"hello","ll"}) -- equivalent to string.find("hello","ll")

    Luaのunpack関数はC言語で実装されていますが、わかりやすくするために、Luaで再帰的に同じ効果を得るには、例えば次のようにします。
コピーコード コードは以下の通りです。

function unpack(t,i)
    i = i or 1
     if t[i] then
         return t[i], unpack(t,i + 1)
     end
end

2. 可変長のパラメータ。
    Luaの関数は、異なる数の実パラメータを受け取ることができ、それらは次のように宣言して使用します。

コピーコード コードは以下の通りです。

 function add(...)
    local s = 0
    for i, v in ipairs{...} do
        s = s + v
    end
    return s
end
print(add(3,4,5,6,7))
-- The output is: 25

  説明すると、関数宣言の(...)は、その関数が異なる数の引数を受け取ることができることを示します。この関数が呼び出されると、すべての引数がまとめられ、やはり関数内で3つの点(...)でアクセスする必要がある。しかし、この3つの点はその後、{...}がすべての変数引数の配列を表すというように、式として使われるようになるという違いがあります。可変長の引数を持つ関数では、固定引数を持つことも可能ですが、固定引数は可変長の引数の前に宣言しなければなりません、例えば、次のようになります。

コピーコード コードは以下の通りです。

    function test(arg1,arg2,...)
        ...
    end

    Luaの可変長パラメータに関する最後の注意点として、可変長パラメータにはnil値が含まれることがあるため、テーブルの要素数を取得するような方法で再度可変長パラメータの数を取得するのは問題がある(#)ということです。常に正しい引数の数を取得するためには、Luaが提供するselect関数などを利用するとよいでしょう。
コピーコード コードは以下の通りです。

for i = 1, select('#',...) do -- Here the '#' value indicates the number of variable parameters to let select return (which includes nil).
    local arg = select(i, ...) -- where i means get the i-th variable parameter, 1 being the first.
     -- do something
end

3. 実パラメータを命名。

    関数呼び出しにおいて、LuaはC言語と同じ受け渡しルールを持っており、名前付き実パラメータを実際にサポートしていません。しかし、例えばテーブルでこれをエミュレートすることができます。

コピーコード コードは以下の通りです。

    function rename(old,new)
        ...
    end

    ここで、上記のリネーム関数は引数をひとつだけ取り、その型はテーブル型とします。
コピーコード コードは以下の通りです。

    function rename(arg)
        local old = arg.old
        local new = arg.new
        ...
    end

    この修正は、複数の引数を1つのJavaBeanにまとめるJavaBeanと多少似ています。しかし、Luaのテーブルを使用することで、引数が1つだけで、型が文字列またはテーブルであれば、例えば括弧なしで関数を呼び出すことができるという、自然な利点があります。
コピーコード コードは以下の通りです。

    rename {old = "oldfile.txt", new = "newfile.txt"}

II. 深化した機能

    Luaの関数は、他の値と同様に匿名です。つまり、名前を持ちません。使用する際には、その関数を保持する変数に対して操作を行います。

コピーコード コードは以下の通りです。

    a = { p = print }
    a.p("Hello World")
    b = print
    b("Hello World")

    Luaの関数を宣言するときは、いわゆる関数名だけで、たとえば
コピーコード コードは以下の通りです。

    function foo(x) return 2 * x end

    また、Luaで関数を宣言する場合、より簡略化した以下のような方法もあります。
コピーコード コードは以下の通りです。

    foo = function(x) return 2 * x end

    このように、関数は、変数に値を代入しながら、文からなる型値として理解することができる。このことから、quot;function(x) <body> end"という式は、表の{}のような関数コンストラクタと考えることができる。このような関数コンストラクタの結果を、quot;匿名関数と呼びます。次の例は、無名関数の利便性を示すもので、Javaの無名クラスなどと多少似たような使い方をします。
コピーコード コードは以下の通りです。

    table.sort(test_table,function(a,b) return (a.name > b.name) end)

1. クロージャ(クロージャ機能)。
    ある関数が他の関数の内部に書かれている場合、内部の関数は外部関数のローカル変数にアクセスすることができます。
コピーコード コードは以下の通りです。

function newCounter()
    local i = 0
    return function() -- anonymous function
        i = i + 1
        return i
    end
end
c1 = newCounter()
print("The return value of first call is " . c1())
print("The return value of second call is " . c1())
-- The output is.
-- The return value of first call is 1
--The return value of second call is 2

上記の例では、newCounter()関数をクロージャ関数と呼んでいます。この関数内のローカル変数 i は非ローカル変数と呼ばれ、通常のローカル変数とは異なり、newCounter 関数内の匿名関数からアクセスされ、操作されることになります。そして、newCounter関数が戻った後も、その値は保持され、次の計算に使用することができます。次の呼び出しをみてください。

コピーコード コードは以下の通りです。

function newCounter()
    local i = 0
    return function() -- anonymous function
        i = i + 1
        return i
    end
end
c1 = newCounter()
c2 = newCounter()
print("The return value of first call with c1 is " . c1())
print("The return value of first call with c2 is " . c2())
print("The return value of second call with c1 is " . c1())
-- The output is.
-- The return value of first call with c1 is 1
--The return value of first call with c2 is 1
--The return value of second call with c1 is 2

Luaは、新しいクロージャ変数に値が割り当てられるたびに、異なるクロージャ変数に別々の"非ローカル変数"を持たせるということになります。次の例では、クロージャに基づくより一般的な使用方法を説明します。

コピーコード コードは以下の通りです。

do
    -- Here the original file open function is assigned to "private variable"oldOpen, which is not accessible outside the block.
    local oldOpen = io.open
    -- Add a new anonymous function to determine the legitimacy of this file open operation.
    local access_OK = function(filename,mode) <Check access> end
    -- Point the old io.open function variable to the new function, while calling the old function in the new function to complete the real open operation.
    io.open = function(filename,mode)
        if access_OK(filename,mode) then
            return oldOpen(filename,mode)
        else
            return nil,"Access denied"
        end
    end
end

この上の例は、デザインパターンにおけるデコレータパターンにやや似ている。

2. 非グローバル関数。

    前の小節でお分かりのように、Luaの関数はグローバル変数に直接代入できるだけでなく、ローカル変数やテーブルのフィールドなど、他の種類の変数にも代入することが可能です。実際、Luaライブラリのほとんどのテーブルには、io.readやmath.sinなどの関数が付属しています。この書き方は、C++の構造体とやや似ている。例えば

コピーコード コードは以下の通りです。

    Lib = {}
    Lib.add = function(x,y) return x + y end
    Lib.sub = function(x,y) return x - y end

    あるいは、テーブルのコンストラクタで直接初期化することもできます。
コピーコード コードは以下の通りです。

    Lib = { add = function(x,y) return x + y end,
               sub = function(x,y) return x - y end
             }

    これに加えて、Luaではこのような関数を定義するための別の構文が用意されており、次のようになります。
コピーコード コードは以下の通りです。

    Lib = {}
    function Lib.add(x,y) return x + y end
    function Lib.sub(x,y) return x - y end

    Luaのローカル関数のセマンティクスも、非常にシンプルに理解することができます。Luaはブロックを実行単位とするため、ブロック内のローカル関数はブロック外からはアクセスできません、例えば
コピーコード コードは以下の通りです。

do
     local f = function(x,y) return x + y end
     --do something with f.
     f(4,5)
end 

 このようなローカル関数に対して、Luaは次のような、より簡潔な定義方法も提供しています。

コピーコード コードは以下の通りです。

    local function f(x,y) return x + y end

    と同等の書き込みがあります。
コピーコード コードは以下の通りです。

    local f
    f = function(x,y) return x + y end

  3. 正しいテールコール

    Luaでサポートされているこのような関数呼び出しの最適化の1つに、"テールコールの除去"があります。この関数呼び出しは、次のようなgotoステートメントと考えることができます。

コピーコード コードは以下の通りです。

    function f(x) return g(x) end

    g(x)関数はf(x)関数の最後の文なので、g関数が戻った後にf()関数が実行する命令はなく、g()関数が戻った時にf()関数が呼ばれていたところに直接戻ればいいのです。このことから、Luaインタープリタがg()関数がf()関数のテールコールであることを発見すれば、g()が呼び出されたときに関数呼び出しによるスタックオーバーヘッドが発生しないことがわかります。ここで重要なのは、テールコール関数は呼び出し側関数の最後のステートメントでなければならず、そうでなければLuaは最適化しないことです。しかし実際には、次のようにテールコールのように見えるものが、実際にはテールコールでないというシナリオが多くあります。
コピーコード コードは以下の通りです。

    function f(x) g(x) end -- no explicit hint of a return statement
    function f(x) return g(x) + 1 -- A plus one instruction still needs to be executed after the g() function returns.
    function f(x) return x or g(x) -- If the g() function returns more than one value, this action forces the g() function to return only one value.
    function f(x) return (g(x)) -- Same reason as above.

    Luaでは、"return <func>(<args>)" という形式だけが標準的なテールコールになります。引数(args)に式が含まれているかどうかについては、式の実行は関数呼び出しの前に行われるため、テールコール関数であるかどうかには影響しません。