cv.cm

コーディング · プログラミング言語

Lua入門:初心者向けチュートリアル

約11分で読めます

結論

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
型例補足
nilnil値がないこと。設定していない変数はnilになります
booleantrue, false偽になるのはfalseとnilだけで、0と""は真です
number42, 3.14Lua 5.3以降は整数と浮動小数点数のサブタイプがあります
string"hi", 'hi', [[long]]変更できないバイト列の文字列
table{1, 2, x = 3}唯一のデータ構造
functionfunction() 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)                       -- 2

continue文はありません。::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.0

Luaにはコルーチン(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作りや学習用に人気があります。標準ライブラリが最小限なので、機能を増やすにはホストプログラムやモジュールに頼ることになります。

コーディング講座の一覧