第 3 回: Lua の関数やメタテーブルについて紹介する

関数の定義の仕方

関数の定義 (その 1):

function 関数名 (引数 1, 引数 2, ...)
  関数の内容
end

関数の定義 (その 2):

関数名 = function (引数 1, 引数 2, ...)
  関数の内容
end
-- 以下は関数定義より前で関数を呼び出しているのでエラーになる
f()

function f()
  print "called f()"
end

可変引数の利用

my_printf.lua

1
2
3
4
5
6
7
8
-- my_printf.lua

function my_printf(fmt, ...)
   print(fmt:format(...))
end

my_printf("[%s=%s]", "neko", 30)
my_printf("[%s=%s=%s]", "feiefi", "neko", 30)

my_printf.lua の実行結果は:

[wtopia lua.hajime]$ lua my_printf.lua
[neko=30]
[feiefi=neko=30]

可変引数を一つずつ順に取り出したい場合は, テーブルコンストラクタで変数に代入し, これを利用することができる.

para.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 可変引数の値を一つずつ表示する方法

function func(...)
   n = {...}
   for i in pairs(n) do
      print(i)
   end
end

func("a", "b", "c")
func(1, 2, 3)

para.lua の実行結果は:

[wtopia lua.hajime]$ lua para.lua
1
2
3
1
2
3

ローカル変数

Lua では, 関数の中で新規に作った変数はすべてグローバルになってしまう.

ローカル変数には local というキーワードをつける.

local.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- local.lua
-- ローカル変数のテスト

function f(n)
   local v -- local varibable
   v = n
end

v = 10 -- global variable
f(2)
print(v)

local.lua の実行結果は:

[wtopia lua.hajime]$ lua local.lua
10

複数の戻り値

Lua の関数は, [return v1, v2, v3, ...] のようにして複数の戻り値を持つことができる.

そして, その戻り値を受け取るには, 複数の変数を指定した代入文を使うことができる

return.lua

1
2
3
4
5
6
7
8
-- 複数の戻り値を返す

function f(n)
   return (n), (n*2), (n*3)
end

v1, v2, v3 = f(2)
print(v1, v2, v3)

return.lua の実行結果は:

[wtopia lua.hajime]$ lua return.lua
2     4       6

以下のような使い方もある. 一度, テーブルコンストラクタにいれて, pairs() でテーブルの値を列挙する.

table_ret.lua

1
2
3
4
5
6
7
8
-- table_ret.lua
function f(n)
   return (n), (n*2), (n*3)
end

for i in pairs({f(2)}) do
   print(i)
end

table_ret.lua の実行結果は:

[wtopia lua.hajime]$ lua table_ret.lua
1
2
3

このように, 可変引数をテーブルに変換する場合は, { 関数() } のように書くことができる. そして, テーブルから可変引数に変換するには, uppack() を使うことができる.

unpack.lua

1
2
3
4
-- unpack.lua
t = {1, 2, 3}
print(t) -- table のアドレスが表示される
print( unpack(t) ) -- 1 2 3 が表示される

unpack.lua の実行結果は:

[wtopia lua.hajime]$ lua unpack.lua
table: 0x100105fc0
1     2       3

引数が文字列で一つの場合は特別扱いされる

onearg.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- onearg.lua
-- 引数が文字列で一つの場合は OK
print "Hello, Lua World!"

-- 以下はエラーになる
name = "Fei Zhao"
-- print "Hello!"..name -- 式がある場合エラー
print("Hello,"..name) -- カッコで付ければ OK

-- 引数が一つでも変数だと NG
value = 50
-- print value

onearg.lua の実行結果は:

[wtopia lua.hajime]$ lua onearg.lua
Hello, Lua World!
Hello,Fei Zhao

マニュアルを見てみると, 引数の定義が以下のようになっており, 文字列一つなら, 丸括弧をつけずに呼び出すことができるようになっていた:

関数の引数の書式
args ::= tableconstructor
args ::= String

テーブルのコンストラクタをそのまま記述できるというところ. つまり, func({1, 2, 3}) を func{1, 2, 3} と書くことができるということ.

以下は, 関数呼び出しにテーブルコンストラクタを指定する例

functable.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- functable.lua

function f(n)
   fmt = "[%s:%d]"
   print(fmt:format(n["name"], n["age"]))
end
-- 関数を呼び出し
f{name="feifei", age=28}
f{name="yangyang", age=8}

f({name="yangguang", age=2}) -- 普通の呼び出し方
f({name="kankan", age=1}) -- 普通の呼び出し方

functable.lua の実行結果は:

[wtopia lua.hajime]$ lua functable.lua
[feifei:28]
[yangyang:8]
[yangguang:2]
[kankan:1]

レキシカルスコープ (lexical scope) について

レキシカルスコープというのは, 関数の中でローカルな関数を定義し, そのローカルな関数の中で, 外側の関数のローカル変数を利用できるというもの

localfunc.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- localfunc.lua
-- 関数の中で関数を定義

function outer()
   
   local outer_a = 30
   
   local function inner1()
      outer_a = outer_a + 1
      print(outer_a .. " local function inner1 is called")
   end

   local function inner2()
      outer_a = outer_a + 1
      print(outer_a .. " local function inner2 is called")
   end
   
   inner1()
   inner2()
   inner2()
   inner1()

end

-- 関数を呼び出す
outer()

localfunc.lua の実行結果は:

[wtopia lua.hajime]$ lua localfunc.lua
31 local function inner1 is called
32 local function inner2 is called
33 local function inner2 is called
34 local function inner1 is called

これを利用することで, 関数を返す関数を定義することができる. これにより, ある関数の引数を省略した関数を定義することができる.

以下のプログラムは, 関数 pset を定義し, この関数の引数 color を省略して呼び出すことができる. pset_black と pset_white 関数を生成する

exfuncinfunc.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- exfuncinfunc.lua
-- 座標 (x, y) に color の点を打つ関数を定義

function pset(x, y, color)
   print(string.format("pset(%d, %d, %06x)", x, y, color))
end

-- 関数を返す関数を定義
function make_pset_func(color)
   return function (x, y)
	     pset(x, y, color)
	  end
end

-- 特定の色を描画する関数を作成する
pset_black = make_pset_func(0x0000FF)
pset_white = make_pset_func(0xFFFFFF)
pset_red = make_pset_func(0xFF0000)

-- 関数を呼ぶ
pset_black(10, 10)
pset_white(50, 50)
pset_red(70, 70)

exfuncinfunc.lua の実行結果は:

[wtopia lua.hajime]$ lua exfuncinfunc.lua
pset(10, 10, 0000ff)
pset(50, 50, ffffff)
pset(70, 70, ff0000)

for ... in 構文について補足

for ... in 構文について, in の後ろに, テーブルや関数の戻り値を記述することはできないが,

ただし, 戻り値に関数を指定し, その戻り値を返すと正しく動かすことができる.

forin.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- forin.lua
-- エラーの場合
--[[
function f()
   return 1, 2, 3
end

for i in f() do
   print(i)
end
]]

-- 関数を返す関数を定義
function f()
   local i = 1
   local values = {"a", "b", "c"}
   return function()
	     local v = values[i]
	     i = i + 1
	     return v
	  end
end

-- 繰り返し f() を呼ぶ
for i in f() do
   print(i)
end

forin.lua の実行結果は:

[wtopia lua.hajime]$ lua forin.lua
a
b
c

ところで, バージョン 5.0 以前では, table ライブラリに, foreach があったようだが, これは 5.1 以降では廃止されているようだ.

そこで, それの動作に近い, myforeach 関数を定義すると, 以下のようになる.

myforeach.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-- myforeach.lua
-- myforeach の定義
function myforeach(t, f)
   for key, val in pairs(t) do
      f(key, val)
   end
end

-- 使ってみる
t = {ff=28, yy=8, yg=2, kk=1}
myforeach(
   t, function(key, val)
	 print(key, val)
      end
)

myforeach.lua の実行結果は:

[wtopia lua.hajime]$ lua myforeach.lua
yy    8
kk    1
yg    2
ff    28

メタテーブルについて

メタテーブルとは, 実際の値とは関係のない付加的な属性やパラメータを覚えておくことができる.

メタテーブルへの読み書きは, setmetatable() と getmetatable() で行うことができる. 以下は, 変数 v の値にメタテーブルを設定し, v の内容とそのメタテーブルの内容を列挙するプログラム.

metatable.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- metatable.lua

-- v をテーブルとして初期化する
v = {name="feifei", age=28, sex="male"}

-- メタテーブルを追加する
setmetatable(v, {memo1="test1", memo2="test2"})

-- v の値を列挙する
print "*** values"

for key, val in pairs(v) do
   print(key, val)
end

-- meta テーブルを取得して列挙する
print "*** metatables"
meta = getmetatable(v)

for key, val in pairs(meta) do
   print(key, val)
end

metatable.lua の実行結果は:

[wtopia lua.hajime]$ lua metatable.lua
*** values
age   28
name  feifei
sex   male
*** metatables
memo1 test1
memo2 test2

メタテーブルは, 実際の値に何の変化も与えないというところがポイントである.

このメタテーブルを演算子の定義と, オブジェンクト指向に利用することができる.

演算子の定義について見てみよう

metatable2.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- metatable2.lua
-- テーブル同士の足し算を定義

function table_add(a, b)
   local c = {}
   for key, val in pairs(a) do
      c[key] = val
   end
   for key, val in pairs(b) do
      c[key] = val
   end
   return c
end

-- テーブルを作成
t1 = {a = 1, b = 2, c = 3}
t2 = {d = 4, e = 5, f = 6}

-- メタテーブルを定義
setmetatable(t1, {__add=table_add}) -- __add を使わなければならない

-- テーブル同士の足し算を行って結果を表示
t3 = t1 + t2

for i, v in pairs(t3) do
   print(i, v)
end

metatable2.lua の実行結果は:

[wtopia lua.hajime]$ lua metatable2.lua
a     1
c     3
b     2
e     5
d     4
f     6

metatable3.lua

1
2
3
4
5
6
7
8
9
-- metable3.lus
obj = {1, 2, 3, 4, 5, 6}

function plus(x, y)
   return x[1]+y
end
setmetatable(obj, {__add=plus})

print(obj+5)

metatable3.lua の実行結果は:

[wtopia lua.hajime]$ lua metatable3.lua
6