-
Notifications
You must be signed in to change notification settings - Fork 1
Language
だいたいのプログラミング言語と同様に、Lua でも関数呼び出しの機能があり、通常は関数名の後に括弧で囲んだ引数リストを伴って呼び出す。たとえば、次のような形である。
~~codetmp(function(spin)
local res = func(1, 2, 3)
~~end)
このような関数を定義するには、次のように書く。
~~codetmp(function(spin)
function func (a, b, c)
-- a、b、c を使って何かする
return result
end
~~end)
これは、func という名前の関数を定義していると考えることができるが、じつは、次のような書き方と同じ意味になる。
~~codetmp(function(spin)
func = function (a, b, c)
-- a、b、c を使って何かする
return result
end
~~end)
何が違うかというと、function から end までの間に記述された 無名関数 を func という変数に代入しているのだ。もう少し細かくいうと、関数と変数が同じ名前空間に属するということと、JavaScript のように関数の定義のところで実行順が変わったりしないということができる。
また、func が単なる変数であるということは、次のようにローカル変数にすることもできる。
~~codetmp(function(spin)
local func = function (a, b, c)
-- a、b、c を使って何かする
return result
end
~~end)
これも次のように少し短く書くことができる。
~~codetmp(function(spin)
local function func (a, b, c)
-- a、b、c を使って何かする
return result
end
~~end)
つまり、頭に local をつければ、その関数はローカルのスコープを持つということである。
多くのプログラミング言語では、関数は一度に 1 個しか値を返すことができないが、Lua では任意の数の値を一度に返すことができる。この時、関数が 多値 を返すといったりする。たとえば、3 個の引数をとって、これらをそれぞれ 3 倍して返す関数は次のように書ける。
~~codetmp(function(spin)
function three_times (a, b, c)
return a * 3, b * 3, c * 3
end
~~end)
このように、返したい値をカンマで区切って、return すればよい。
こうして返された多値は、次のようにして受け取る。
~~codetmp(function(spin)
local x, y, z = three_times(1, 2, 3)
~~end)
代入文で、複数の変数をカンマで区切って指定するだけである。
多値をそのまま、関数の引数とすることもできる。たとえば、3 個の引数をとってそれらの合計を計算する次のような関数があるとする。
~~code("sum.lua", function(spin)
function sum (a, b, c)
return a + b + c
end
~~end)
この関数に対して、
~~code("multi-value-compose.lua", function(spin)
local x = sum(three_times(1, 2, 3))
~~end)
というふうに呼び出すと、three_times からの 3 個の戻り値がそのまま sum の 3 個の引数として渡される。
このように、関数が返す多値と関数へ渡す引数の間には対照的な関係がある。これは Lua の面白い特徴のひとつである。
多値は複数の値をひとまとめにしたものなので、一見したところ配列(テーブル)と同じように思えるかもしれないが、多値をデータ構造として扱うことはできない。ただし、多値あるいは引数の列を配列に変換したり、配列を多値に変換することはできる。
引数の列を配列にするには、次のように ... という記号を使う。
~~code("maltivalue-array.lua", function(spin)
function three_times2 (...)
local vals = {...}
for i, v in vals do
vals[i] = 3 * v
end
-- 計算結果を返す……
end
~~end)
逆に配列を多値に展開するには table.unpack という関数を使う。これを使って上の関数を完成させると、次のようになる。
~~code("maltivalue-array-2.lua", function(spin)
function three_times2 (...)
local vals = {...}
for i, v in vals do
vals[i] = 3 * v
end
return table.unpack(vals)
end
~~end)
また、配列を引数リストとして関数を呼び出すにも table.unpack を使う。これは JavaScript や Scheme などの言語における apply と同様のことをする。
~~code("array-maltivalue.lua", function(spin)
local numbers = {1, 2, 3, 4}
local a, b, c, d = three_times2(table.unpack(numbers))
~~end)
このように配列と多値や引数リストの間での変換が自在にできるようになると、非常に柔軟なプログラミングができるようになる。
クロージャとは、関数とそれを取り巻く「環境」を、無名関数の形で閉じ込めたものである。 それは関数のように呼び出すことができ、評価すると値を返す。 しかし、普通の関数と違って、クロージャにはそれぞれ固有の状態を持つことが出来る。
状態を持ち、関数として呼び出すことが出来るという点で、C++ の関数オブジェクトに近い。 ただし、関数オブジェクトと違って、いちいち名前をつける必要がないし、クラスを宣言する必要もない。 また、関数呼び出しというシンプルなインターフェイスしか持たないため、 どんなところでも使うことが出来る。
例を示すために、まずは通常の関数を考える。たとえば与えられた数値を 4 倍した数値を返す関数 four_times は次のように書ける。
~~code("eg/4times.lua", function (spin)
function four_times(m)
return m * 4
end
~~end)
ここで、4 倍するのに飽きて 5 倍にする関数が欲しくなったとしよう。このとき、four_times の関数定義をコピーして five_times 関数を作るのは簡単だが、飽きるたびに新しい関数を作っていてはきりがない。そこで、好きな数値 n を与えると、「n 倍する関数」を作ってくれる関数を作ると便利だろう。
これは、Lua では次のように簡単に作ることができる。
~~code("eg/closure.lua", function (spin)
function n_times(n)
return function(m)
return m * n
end
end
~~end)
ここで、function が 2 重に入れ子になっているて、内側の function で作った関数が、外側にある n_times という関数の返り値となっていることが分かる。また、内側の関数には名前がついてなくて、function のすぐ次に引数リストが来ていることにも注意してほしい。このような関数は名前がついてないので無名関数と呼ばれる。
無名関数は、変数に代入したり、関数の引数にしたり、あるいは関数からの返り値にすることができる。このような性質から、無名関数は数値や文字列などと同様の第一級オブジェクト(first-class object)といったりする。
さてこの内側の関数をよく見ると、外側の関数から持ち込んだ変数 n を参照しているのが分かる。この n はグローバル変数ではない。なぜなら、n_times 関数の内側でしか使えないからだ。このように、関数の外側で宣言され、かつグローバルでもない変数を参照することができる。このような変数のスコープのことをレキシカルスコープという。
上で紹介したような無名関数とレキシカルスコープを組み合わせて作った関数のことをクロージャと呼ぶ。クロージャは日本語では閉包と言われたりもする。これは関数に状態を閉じ込めて包んだものである。この状態とは何か、以下で考えてみる。
上の n_times は、一度関数を作ってしまうと、その関数はいつ、何度呼んでも、引数が同じならば同じ結果を返した。では次に、呼び出すたびに値が変化するような関数を考えよう。たとえば、呼び出すたびに値が 1 づつ大きくなる関数を作ってみる。
~~codetmp(function(spin)
count = 0
function count_up()
count = count + 1
return count
end
~~end)
この関数は確かに呼び出すたびに 1 づつ大きな整数を返す。しかし、大域変数を使っているためバグを生みやすい。また大域変数を使っているために、同じ動作をする関数をもうひとつ作るには、もうひとつ大域変数を用意しなくてはいけない。クロージャを使うと次のようにかける。
~~codetmp(function(spin)
function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
~~end)
メタテーブル。