高速スクリプト言語「Lua」を始めよう!(3)

146 views
Skip to first unread message

sagasw

unread,
Aug 7, 2009, 5:47:40 AM8/7/09
to lu...@googlegroups.com

本稿では動作速度が高速で、非常に移植性が高い組み込み向けのプログラミング言語「Lua」の使い方について紹介します。Lua は、JavaScript や Pascal に似ていることから、とても手に馴染みやすいのが特徴です。自作アプリケーションにちょっとしたスクリプト言語を組み込みたい場合に重宝します。そこで、 Lua のインストールから、簡単な使い方を紹介し、簡単なアプリケーションに組み込んで使うまでの過程を解説します。

本稿の目標

本稿では、Lua がどんなプログラミング言語なのかを紹介し、実際に簡単なアプリケーションに組み込んで使ってみるところまでを紹介します。

第一回目は、Lua とは何か、そして、どんな風にプログラムを書くのかを紹介しました。第二回目は、Lua の便利なテーブル型や文字列の操作について紹介しました。今回は、関数やメタテーブルについて見ていきます。

関数の定義の仕方

それではさっそく、Luaの関数について見ていきます。Luaの関数定義は以下のような2種類の方法で記述することができます。その1の方は割と一般的な書き方ですが、その2の方は、無名関数の記述方法です。

JavaScript でも、これら2つの書き方と同じように記述することができますが、その場合、この2つは別の意味を持ちます。しかし、Lua では、以下の2つは全く同じ意味になります。

【関数の定義(その1)】

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

【関数の定義(その2)】

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

そのため、以下のように関数定義より前で関数を使用することはできませんので注意が必要です。(これは、JavaScriptと違うところです。)

-- 以下は関数定義より前で関数を呼び出しているのでエラーになる
f()

function f()
print "called f"
end
可変引数の利用

関数の引数にデフォルト値を指定することはできませんが、可変引数を指定することが可能となっています。

可変引数を利用するには、関数定義の引数の最後に「…」と記述します。そして、省略された引数を使用するところで、「…」を記述します。

-- my_printf を作ってみた
function my_printf(fmt,...)
print(fmt:format(...))
end

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

それでは、可変引数を1つずつ順に取り出したい場合などはどうすれば良いのかと言えば、一度、テーブルコンストラクタで変数に代入し、これを利用することができます。

-- 可変引数の値を1つずつ表示する方法
function f(...)
n = {...}
for i in pairs(n) do
print(i)
end
end

f(1,2,3)

表示結果:

1
2
3

ローカル変数

それから、関数を定義する場合に必須となるのが、ローカル変数です。Lua では、関数の中で新規に作った変数はすべてグローバルになってしまいます。ローカル変数を作るには、変数や関数を利用するときに、local というキーワードをつけます。

以下のプログラムでは、関数 f の中で local v という宣言を行っているので、変数 v がローカル変数となり、関数の中で値を書き換えても、グローバルな v に影響を及ぼすことがありません。

-- ローカル変数のテスト
function f(n)
local v
v = n
end

v = 10
f(2) -- この中で v を書き換え?
print(v)

複数の戻り値

Lua の関数は、「return v1,v2,v3..」のようにして複数の戻り値を持つことができます。そして、その戻り値を受け取るには、複数の変数を指定した代入文を使うことができます。

-- 複数の戻り値を返す
function f(n)
return (n),(n*2),(n*3)
end

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

ただし、戻り値を、for .. in 構文に直接かけることはできません。

-- 関数の戻り値を1つずつ表示する場合
-- ※注意:エラーになる
function f(n)
return (n),(n*2),(n*3)
end
-- for .. in では処理できない
for i in f(2) do -- ここでエラー
print (i)
end

そのため、以下のように、一度、テーブルコンストラクタに入れて、pairs() でテーブルの値を列挙するという手続きを踏まなくてはなりません。

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

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

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

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

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

ところで、第一回目で、Lua の Hello, World を作ってちょっと不思議に思ったことがありました。それは、「print "Hello, World"」と書くことができるのに、「print "Hello "..name」のように式を書くことができないのです。以下のプログラムを見てください。

-- 引数が文字列で1つの場合は OK
print "Hello, Wolrd!"

-- 以下はエラーになる
name = "KUJIRA"
print "Hello!"..name -- 式がある場合エラー
print ("Hello!"..name) -- カッコで括ればOK
-- 引数が1つでも変数だと NG
value = 50
print value

print の引数に式を指定したい場合は、print("Hello "..name) と丸括弧で引数を囲う必要があるのです。

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

※関数の引数の書式
args ::= `(´ [explist1] `)´
args ::= tableconstructor
args ::= String

そして、この定義を見て興味深く感じたのが、テーブルのコンストラクタをそのまま記述できるというところです。つまり、func( {1,2,3} ) を func{1,2,3} と書くことができるということです。

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

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

実行結果:

[jiro:8]

レキシカルスコープについて

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

言葉で書くと複雑ですが、それほど難しいことではありません。次の例を見てください。

-- 関数の中で関数を定義
function outer()
local outer_a = 30 -- 外側の関数でローカル変数を定義
local function inner()
print(outer_a) -- 内側の関数で、外側の関数の変数を利用する
end
inner()
end
-- 関数を呼び出す
outer()

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

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

-- 座標(x,y) に color の点を打つ関数を定義
function pset(x,y,color)
print(string.format("pset(%d,%d,%d)",x,y,color))
end
-- 関数を返す関数を定義
function make_pset_func(color)
return function (x,y)
pset(x,y,color)
end
end
-- 特定の色を描画する関数を作成する
pset_black = make_pset_func(0)
pset_white = make_pset_func(0xffffff)
-- 関数を呼ぶ
pset_black(10,10)
pset_white(50,50)

実行結果:

pset(10,10,0)
pset(50,50,16777215)

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

ちなみに、for .. in 構文について、in の後に、テーブルや関数の戻り値を記述することはできません。疑問が湧いたのでマニュアルを調べてみると、ここには explist1(連続する式)を記述することが分かります。関数の戻り値も、explist1 となっていますが、関数を指定するとエラーになります。(これは、複数の戻り値のところで見た通りです。)

-- エラーの場合
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

実行結果

a
b
c

ところで、バージョン5.0以前では、table ライブラリに、foreach があったようですが、これは、5.1以降では廃止されているようです。そこで、それの動作に近い、myforeach 関数を定義すると、以下のようになるでしょうか。

-- my_foreach の定義
function myforeach(t, f)
for key,val in pairs(t) do
f(key, val)
end
end
-- 使ってみる
t = {mike=4,taro=8,iko=3}
myforeach(t, function(key,val)
print(key,val)
end)

メタテーブルについて

Lua の値は、メタテーブルを持つことができるようになっています。メタテーブルとは、実際の値とは関係のない付加的な属性やパラメータを覚えておくことができるものです。

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

-- v をテーブルとして初期化する
v = {name="v",value=40}
-- メタテーブルを追加する
setmetatable(v, {memo="test"})
-- 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

メタテーブルは、実際の値に何の変化も与えないというところがポイントです。では、このメタテーブルを何に使うのかと言えば、演算子の定義と、オブジェクト指向に利用することができます。

オブジェクト指向については、次回考えることにして、今回は、演算子の定義について見てみます。

-- テーブル同士の足し算を定義
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 = {c=4,d=5,e=6}
-- メタテーブルを定義
setmetatable(t1, {__add=table_add})
-- テーブル同士の足し算を行って結果を表示
t3 = t1 + t2
for i,v in pairs(t3) do
print(i, v)
end

まとめ

今回は、Lua の関数やメタテーブルについて紹介しました。特に、関数について紹介しました。Lua ならではの魅力を感じることができたと思います。また、for .. in 構文で使う値リストとテーブルの扱い方の違いについても紹介しました。次回は、さらにメタテーブルを活用した、オブジェクト指向についても考えていきま す。

Reply all
Reply to author
Forward
0 new messages