コーディング · プログラミング言語
Lua入門:初心者向けチュートリアル
結論
Luaは、ゲーム、Roblox(派生言語のLuau経由)、Neovim、Redis、組み込み機器などで使われている、軽量で組み込みやすいスクリプト言語です。構文が小さく、データ構造はテーブル1つだけで、これが配列・辞書・オブジェクトを兼ねます。このページでは、Luaのインストール、変数と型、制御構文、関数、テーブル、メタテーブル、pcallによるエラー処理、そして完成したサンプルを扱います。
Luaとは何か、何に使うのか
Luaは、1993年にブラジルの大学PUC-Rioで生まれた、小さくて高速な動的型付けのスクリプト言語です。インタープリター全体がC言語で書かれたコンパクトなライブラリなので、大きなプログラムの中に簡単に組み込めます。データ構造はテーブルだけで概念も少ないため、短期間で習得できます。
Luaは主に組み込み用のスクリプト言語として使われています。ゲームエンジンやゲーム(RobloxはLuauというLuaの派生言語を使い、LÖVEやDefoldはLuaを使います)、エディターのNeovim、データベースのRedis、OpenRestyを使ったNginxなどのネットワークツール、さまざまな組み込み機器が例です。LuaJITは、Lua 5.1のコード向けの代表的なJIT(ジャストインタイム)コンパイラです。
LuaのインストールとHello, worldの実行
Luaは、パッケージマネージャー(たとえばbrew install luaやapt install lua5.4)でインストールするか、lua.orgのソースからビルドします。luaだけを実行すると対話プロンプトが起動します。
-- hello.lua
print("Hello, world!")lua hello.lua
# Hello, world!コメントは--で始まります。ブロックコメントは--[[ ... ]]と書きます。文末にセミコロンは必要ありません。
変数と基本の型
変数はデフォルトでグローバルになります。本当にグローバルにしたい場合を除き、必ずlocalを付けて宣言しましょう。Luaの基本型は8種類あり、日常的によく使うものを下の表にまとめました。
local name = "Lua"
local year = 1993 -- integer
local pi = 3.14159 -- float
local ok = true
local nothing = nil
print(type(name), type(year), type(nothing)) -- string number nil
print(math.type(year), math.type(pi)) -- integer float
print(name .. " " .. year) -- Lua 1993 (.. joins strings)
print(#name) -- 3 (length)
print(7 / 2, 7 // 2, 7 % 2, 2 ^ 10) -- 3.5 3 1 1024.0
print(string.format("%.2f", pi)) -- 3.14
print(tonumber("42") + 1, tostring(10)) -- 43 10| 型 | 例 | 補足 |
|---|---|---|
| nil | nil | 値がないこと。設定していない変数はnilになります |
| boolean | true, false | 偽になるのはfalseとnilだけで、0と""は真です |
| number | 42, 3.14 | Lua 5.3以降は整数と浮動小数点数のサブタイプがあります |
| string | "hi", 'hi', [[long]] | 変更できないバイト列の文字列 |
| table | {1, 2, x = 3} | 唯一のデータ構造 |
| function | function() end | 第一級の値 |
「等しくない」演算子は!=ではなく~=です。論理演算子はand、or、notという単語です。
制御構文
ブロックはキーワードendで閉じます。Luaにはif/elseif/else、while、repeat ... until、数値のfor、イテレーターを扱える汎用のforがあります。
local score = 72
if score >= 90 then
print("A")
elseif score >= 70 then
print("B") -- B
else
print("C")
end
for i = 1, 3 do -- 1 to 3, inclusive
io.write(i, " ") -- 1 2 3
end
print()
for i = 10, 1, -4 do io.write(i, " ") end -- 10 6 2
print()
local n = 3
while n > 0 do n = n - 1 end
repeat
n = n + 1
until n >= 2
print(n) -- 2continue文はありません。::continue::ラベルとgoto continueを使うか、ifでループの構造を組み替えてください。
関数
関数は値です。...で可変個の引数を受け取れ、複数の値を返せます。クロージャは外側のスコープのローカル変数を捕捉します。
local function add(a, b)
return a + b
end
local function minmax(t)
local lo, hi = math.huge, -math.huge
for _, v in ipairs(t) do
lo, hi = math.min(lo, v), math.max(hi, v)
end
return lo, hi -- two return values
end
local function counter()
local count = 0
return function() -- a closure
count = count + 1
return count
end
end
print(add(2, 3)) -- 5
local lo, hi = minmax({4, 9, 1})
print(lo, hi) -- 1 9
local next_id = counter()
print(next_id(), next_id()) -- 1 2
local function sum(...)
local total = 0
for _, v in ipairs({...}) do total = total + v end
return total
end
print(sum(1, 2, 3)) -- 6テーブル:配列・辞書・オブジェクト
テーブルは連想配列で、nil以外のどんな値でもキーにできます。この1つの構造が、配列、辞書、集合、モジュール、オブジェクトの役割を果たします。慣例として、配列の添字は1から始まります。
local fruits = {"apple", "banana"}
table.insert(fruits, "cherry")
print(#fruits, fruits[1]) -- 3 apple
for i, f in ipairs(fruits) do -- array part, in order
print(i, f)
end
local ages = {ann = 31, bo = 25}
ages["cy"] = 40
ages.bo = nil -- remove a key
for k, v in pairs(ages) do -- all keys, order not defined
print(k, v)
end
table.sort(fruits, function(a, b) return a > b end)
print(table.concat(fruits, ", ")) -- cherry, banana, appleメタテーブルとオブジェクト指向のLua
メタテーブルはLuaを代表する機能です。テーブルにメタテーブルを付けると、そのテーブルの振る舞いを変えられます。__indexは存在しないキーの値を補い(継承に使われます)、__add、__eq、__tostringなどのフィールドは演算子をオーバーロードします。コロン構文obj:method()は、オブジェクトを隠れた第1引数selfとして渡します。
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({x = x, y = y}, Vector)
end
function Vector:length()
return math.sqrt(self.x ^ 2 + self.y ^ 2)
end
Vector.__add = function(a, b) return Vector.new(a.x + b.x, a.y + b.y) end
Vector.__tostring = function(v) return "(" .. v.x .. ", " .. v.y .. ")" end
local v = Vector.new(3, 4) + Vector.new(0, 0)
print(tostring(v)) -- (3, 4)
print(v:length()) -- 5.0Luaにはコルーチン(coroutine.create、coroutine.resume、coroutine.yield)もあり、関数を途中で一時停止して後から再開できます。ゲームのスクリプトでは、複数フレームにまたがる段階的な動きを書くときによく使われます。
エラー処理
エラーはerror(message)で発生させます。pcall(保護呼び出し)で捕捉すると、プログラムが止まる代わりにfalseとエラーが返ります。io.openのように、エラーを発生させずnilとエラーメッセージを返すライブラリ関数も多くあります。
local function divide(a, b)
if b == 0 then
error("division by zero")
end
return a / b
end
local ok, result = pcall(divide, 10, 2)
print(ok, result) -- true 5.0
ok, result = pcall(divide, 1, 0)
print(ok, result) -- false script.lua:3: division by zero
local f, err = io.open("missing.txt", "r")
if not f then print("open failed: " .. err) endエラーメッセージに含まれるファイル名と行番号は、コードが実行される場所によって変わります。assert(value, message)はvalueがfalseまたはnilのときにエラーを発生させるので、結果を手早く確認する方法として便利です。
小さな完成プログラム:単語カウント
このプログラムは、パターンマッチで文章中の単語を数え、出現頻度順に並べて上位3つを表示します。文字列パターン、テーブル、独自の比較関数によるソート、文字列の書式化を使っています。
local text = [[
The cat and the dog. The dog sat,
and the cat ran.
]]
local counts = {}
for word in text:lower():gmatch("%a+") do
counts[word] = (counts[word] or 0) + 1
end
local list = {}
for word, n in pairs(counts) do
list[#list + 1] = {word = word, n = n}
end
table.sort(list, function(a, b)
if a.n ~= b.n then return a.n > b.n end
return a.word < b.word
end)
for i = 1, 3 do
print(string.format("%-4s %d", list[i].word, list[i].n))
end
-- the 4
-- and 2
-- cat 2次に学ぶこと
- lua.orgにあるLua Reference Manual。短くて、必要なことが網羅されています。
- Luaの作者の一人であるRoberto Ierusalimschyの『Programming in Lua』。初版はオンラインで無料で読めます。
- Neovim用のLuaガイド、LÖVE、RobloxのLuauドキュメントなど、使っているホスト環境のドキュメント。
- LuaRocks。Luaモジュールのパッケージマネージャーです。
よくある質問
Luaの配列はなぜ1から始まるのですか?
Luaの設計者は、数学や、プログラマーでない人にとって一般的な数え方に合わせて、1始まりの添字を選びました。ipairs、#、table.insertといった標準ライブラリの関数は、いずれもシーケンスが1から始まることを前提にしています。
LuaはPythonより速いですか?
標準のLuaインタープリターは小さく、動的言語としては一般に高速です。LuaJITは多くの処理でさらに大幅に速くなります。実際の速度は処理内容によって変わるので、自分のケースで計測してください。
pairsとipairsの違いは何ですか?
ipairsは添字1から順に配列部分をたどり、最初のnilで止まります。pairsはテーブルのすべてのキーを、順序を保証せずに走査します。
Luauとは何ですか?
LuauはLua 5.1から派生し、Robloxが開発している言語です。段階的型付けと性能改善が加わっており、Robloxのゲームで使われています。
Luaは初心者に向いていますか?
はい。構文が小さく一貫していて概念も少ないため、ゲームのMOD作りや学習用に人気があります。標準ライブラリが最小限なので、機能を増やすにはホストプログラムやモジュールに頼ることになります。