くろねこさんがLuaを覚えたがっていたので。
— くろねこさん (@laprasDrum) 2013年4月18日
ちなみに検証に使用したLuaのバージョンは現時点での最新版である5.2.2です。
LuaでHello World
まずはお決まりのHello Worldから。書き方は3種類くらいあります。
-- はじめてのLuaプログラム print("Hello World")
-- シングルクォートで囲う例 print('Hello World')
-- [[ 〜 ]] はヒアドキュメントのための構文 print([[Hello World]])
上記の3つのコードはいずれも文字列リテラルを表現する構文ですが、挙動が多少異なります。たとえば、
print("こんにちは\n今日はよい天気です")
というコードは、以下のように書いた場合と同様の実行結果になります。
print([[こんにちは 今日はよい天気です]])
Luaの変数
Luaは動的型付け言語なので、特に型指定なしに変数を使用することができます。
一般的な静的型付け言語では整数と浮動小数点数は区別されますが、Luaならば以下のようなスクリプトもエラーなく実行できます。
x = 10 y = 3.14 print(x + y)
なお、実行結果は「13.14」がコンソール上に表示されます。
文字列の場合、リテラルを連結するための特殊な構文が存在します。
str1 = "こんにちは" str2 = "たーせるです" print(str1..str2) -- 「..」で文字列を連結します
実行結果はこんな感じです。
なお、C++ の null に相当するものは、Lua では nil 型として扱われます。たとえば未初期化の変数は nil になりますし、使用済みの変数を以後使用不可にするため、明示的に nil を代入することもあります。
グローバル変数とローカル変数
Lua の変数は、何もしないと自動的にグローバル変数になります。ただし、変数を作る際、変数名の前に local
を付けるとローカル変数として扱われるようになります。
以下に例を示します。do
〜end
ブロック(C++の波括弧に相当)の中で作られたローカル変数は、ブロックの外に出るときに壊されてしまいます。
g_variable = 100 -- グローバル変数 print(g_variable) do local l_variable = "hoge" -- ローカル変数 print(l_variable) end print(g_variable) print(l_variable) -- nilになる
多重代入
Luaには多重代入が可能な性質があります。たとえば以下のように書くと、変数 a には10、b には 20、c には 30がそれぞれ代入されます。
a, b, c = 10, 20, 30 -- a ← 10, b ← 20, c ← 30
すべての値を評価後に代入処理が実行されるため、2変数の値を交換するために一時変数を確保する必要はありません。
x = 10 y = 20 print("x = "..x) print("y = "..y) print() x, y = y, x -- これで変数が入れ替わる print("x = "..x) print("y = "..y)
if文とfor文
構造化プログラミングの基本的な制御構文である if と for の書き方を紹介します。ここは感覚的に理解しやすいところなので、解説控えめで流したいと思います。
-- 変数の初期化 a, b, c = 10, 20, 20 -- a ← 10, b ← 20, c ← 20 -- if then end ステートメント if b == c then print("bとcは等しい") end if a ~= b then print("aとbは等しくない") end if a < b then print("aはbより小さい") end -- forステートメント for i = 1, 3 do print(i) end
波括弧言語になじんでいると、構文の微妙な違いに戸惑うかも知れません。~=
は見慣れない比較演算子ですが、C++ の !=
に相当します。なんで統一しないんだ。
なお、C++の&&
、||
演算子は、Luaではand
、or
になります。なんで統一しないんだ。
ちなみに、Luaでは条件式を短絡評価するため、この性質を利用して変数の値の設定を以下のように書くことができます。
x = 100 x = x or 0 -- 変数 x が非 nil なら x、nil なら 0 を設定 y = y or 0 -- 変数 y が(以下略 print("x = ", x) -- 出力結果: x = 100 print("y = ", y) -- 出力結果: y = 0
これは、変数の設定時に nil が混ざらないようにする比較的有名なテクニックです。
データ構造
Luaは、最も強力かつ唯一のデータ構造として「テーブル」をサポートしています。
テーブルの正体は単なる連想配列(辞書)なので、C++ の std::map や C# の Dictionary に近いものと考えればよいでしょう。ただし Lua の場合、任意の異なる型の情報を一つのテーブルに格納できるため、他言語における連想配列や辞書と必ずしも同一ではありません。
配列
テーブルを配列として使用することができます。ただし、配列のインデックスは1オリジンなので注意が必要です。
-- 配列の初期化 array = { "Hello", "I", "am", "Tercel" } -- Luaの配列は1から始まります -- for文はこんな感じで書きます for i = 1, 4 do print(i.." : "..array[i]) end print("") -- 配列の要素数は、「#配列名」で取得できます for i = 1, #array do print(i.." : "..array[i]) end print("") -- ちなみにforeachに相当する列挙構文もあります for i, v in ipairs(array) do print(i.." : "..v) end print("")
3種類ほどサンプルを書きましたが、いずれも配列のインデックスと要素を列挙します。
多次元配列
配列の要素に配列をセットすることで、多次元配列を再現できます。
matrix = {} -- 行列の生成 for i = 1, 10 do matrix[i] = {} -- 行の作成 for j = 1, 10 do matrix[i][j] = 0 end end
連想配列・構造体
連想配列は、テーブルにキーと値をペアを格納する事で再現できます。構文は、テーブル名["キー"] = 値
です。
なお、テーブル名["キー"]
は、テーブル名.キー
とも書けるので、C言語の構造体のような感覚でデータ操作を行うこともできます。以下のコードをご覧ください。
profile = {} -- 空のテーブルを作成 profile["name"] = "tercel" -- キーと値を指定 profile["age"] = 25 profile["sex"] = "male" print(profile.name) -- テーブル名["キー"] は、テーブル名.キー とも書ける print(profile.age) print(profile.sex)
関数
関数は、function
とend
の間に記述します。
-- 受け取った文字列を表示する関数 function printString(str) print(str) end printString("Hello World")
余談ですが、Luaの関数は複数の戻り値を返すこともできます。
-- 複数の戻り値 function sampleFunction() return 1, 2, 3 end a, b, c = sampleFunction() print("a = ", a) print("b = ", b) print("c = ", c)
なお、Lua は変数に関数を代入できるため、上記コードは以下のように書くこともできます。
sampleFunction = function() return 1, 2, 3 end a, b, c = sampleFunction() print("a = ", a) print("b = ", b) print("c = ", c)
こちらは無名関数を定義し、それを変数 sampleFunction
に代入しています。同様に、テーブルの要素にも関数をセットすることが可能です。
さて……
ここまで読んで下さった方に悲しいお知らせをしますが、この辺から急激に難易度が上がります。内容的にも高度なトピックであることに加えて、他言語で培った知識が今ひとつ役に立たないため、ここまで順調に理解できたとしてもこの先で沈む可能性があります。
それでは怒濤のラスト2章、メタテーブルとクラスをご覧ください。
メタテーブル
Luaにはメタテーブルと呼ばれる特殊なテーブルがあります。ざっくり言うと演算子のオーバーロードに相当する仕組みです。
一つ例を示しましょう。
-- はじめに、ベクトル(のようなもの)を作ります vector1 = { 10, 20, 30 } vector2 = { 100, 200, 300 } -- メタテーブルの定義 -- ここでは、「+」演算子の再定義に相当する処理を行っています metatable = { __add = function(lhs, rhs) local result = {} for i = 1, math.max(#lhs, #rhs) do value1 = lhs[i] or 0 -- lhs[i] が nil なら 0 value2 = rhs[i] or 0 -- rhs[i] が nil なら 0 result[i] = value1 + value2 end return result end } -- vector1, vector2にメタテーブルをセットします -- これにより、vector1, vector2の足し算ができるようになります setmetatable(vector1, metatable) setmetatable(vector2, metatable) -- 実際にやってみましょう vector1 = vector1 + vector2 -- 結果を出力します for i = 1, #vector1 do print(vector1[i]) end
これを実行すると、結果はこんな感じになります。
メタテーブルに登録できるキーのうち、比較的よく使いそうなものを独断と偏見で選んで以下にまとめました。
キー | 意味 | キー | 意味 | |
---|---|---|---|---|
__add | + (二項演算子) | __index | テーブル要素の参照 | |
__sub | - (二項演算子) | __newindex | テーブル要素の追加 | |
__mul | * (二項演算子) | __tostring | テーブルの文字列表現 | |
__div | / (二項演算子) | |||
__mod | % (二項演算子) | |||
__eq | == (比較演算子) | |||
__lt | < (比較演算子) | |||
__le | <= (比較演算子) |
委譲
また、__index
が定義されたメタテーブルを任意のテーブルにセットすると、そのテーブルの挙動が少し変わります。
テーブルに存在しない項目にアクセスしようとしたとき、Luaインタプリタは __index の検索を試みるようになります。百聞は一見に如かずと言いますので、簡単なサンプルをお見せします。
table1 = { x = 100 } table2 = {} -- からっぽのテーブル -- メタテーブルをセット setmetatable(table2, {__index = table1}) -- これで、table2 に存在しない要素を、table1 から探せるようになる print(table2.x)
実行結果を見ると、 x という要素は table2 には存在しないにもかかわらず、表示されていることがわかりますね。table2 に存在しない要素を探すために、__index を介して table1 を参照しているのです。
これが何の役に立つのでしょうか? 実は、Lua をオブジェクト指向的に活用するための強力な仕組みなのです。
クラス
先ほどのメタテーブルを使うと、テーブルをクラスのように利用できるようになります。ここでは簡単なクラスを作ってみます。
これから作ろうとしているものがイメージしやすいよう、Lua で作る前に C++ で書いてみました。C++ だけどポインタ使ってないからこわくないよ。
#include <iostream> // こんなクラスです。 class MyClass { private: std::string str; // メンバ変数 public: MyClass() : str("") {} // コンストラクタ virtual ~MyClass() {} // デストラクタ virtual void setString(std::string string) { str = string; } virtual void printString() const { std::cout << str << std::endl; } }; // こんなふうにつかいます。 int main(int argc, const char * argv[]) { MyClass obj1; obj1.setString("Hello"); obj1.printString(); MyClass obj2; obj2.setString("I am Tercel"); obj2.printString(); return 0; }
いかがでしょう。そこまで複雑なことはしていないので、コードを読めばだいたい何をしているのかお解りいただけると思います。
これを Lua で書くには、メタテーブルの助けが必要です。
-- --------- -- クラス定義 -- --------- MyClass = {} -- 自称コンストラクタ MyClass.new = function() local instance = {} instance.str = "" -- メンバ変数を初期化 setmetatable(instance, {__index = MyClass}) -- メタテーブルをセット return instance end -- メンバ関数(もどき) MyClass.setString = function(instance, string) instance.str = string end MyClass.printString = function(instance) print(instance.str) end -- クラスを使ってみよう obj1 = MyClass.new() obj1.setString(obj1, "Hello") obj1.printString(obj1) obj2 = MyClass.new() obj2.setString(obj2, "I'm Tercel") obj2.printString(obj2)
メンバ関数の第一引数にインスタンス自身を渡しているのは、その関数がどのインスタンスから呼ばれたのかを識別する必要があるからです。C++でも、裏では似たような仕組みが動いており、単にプログラマが意識せずに済んでいるだけに過ぎません。
なお、Luaには糖衣構文があり、上記のスクリプトは以下のように書き換えることができます。これでメンバ関数にわざわざ自分自身を引き渡す必要がなくなります。
-- --------- -- クラス定義 -- --------- MyClass = {} -- 自称コンストラクタ function MyClass:new() local instance = {} instance.str = "" -- メンバ変数を初期化 setmetatable(instance, {__index = MyClass}) -- メタテーブルをセット return instance end -- メンバ関数(もどき) function MyClass:setString(string) self.str = string end function MyClass:printString() print(self.str) end -- クラスを使ってみよう obj1 = MyClass:new() obj1:setString("Hello") obj1:printString() obj2 = MyClass:new() obj2:setString("I'm Tercel") obj2:printString()
ちなみにメンバ関数の中では、メンバ変数は self
を介してアクセスします。self
とは、C++ の this ポインタに相当するものです。
実行してみましょう。最後なのでcat
のおまけつき。
だいぶ長くなってしまったので、今日はここまで。