【完全版】Lua言語の入門から基本操作まで


【完全版】Lua言語の入門から基本操作まで

Lua(ルーア)は、パワフルで軽量、そして組み込み可能なスクリプト言語です。そのシンプルさ、高いパフォーマンス、C/C++との連携の容易さから、ゲーム開発、組み込みシステム、拡張機能の実装など、様々な分野で広く利用されています。

この記事では、Lua言語の全くの初心者でも理解できるよう、環境構築から基本的な文法、データ型、制御構造、関数、テーブル、そしてさらに進んだトピックまで、Luaの基本を網羅的に解説します。約5000語の詳細な説明を通じて、Luaの基礎をしっかりと身につけましょう。

1. はじめに:Luaとは何か?

Luaは、1993年にブラジルのPontifícia Universidade Católica do Rio de Janeiro (PUC-Rio) で開発されたスクリプト言語です。ポルトガル語で「月」を意味する言葉に由来しています。

Luaの最大の特徴は、その軽量さ組み込みやすさです。インタプリタが非常に小さく、メモリ消費も少ないため、リソースが限られた環境(組み込み機器など)や、既存のアプリケーション(C/C++などで書かれたゲームエンジンなど)にスクリプト機能を追加するのに非常に適しています。

また、構文がシンプルで習得しやすく、ガベージコレクションを備えているため、メモリ管理の手間を省くことができます。

Luaが使われている主な分野:

  • ゲーム開発: ゲームエンジンのスクリプティング(Roblox, World of Warcraftのアドオン, Corona SDK, LÖVE2Dなど)。ゲームのロジック、UI、AIなどを記述するのに使われます。
  • 組み込みシステム: ファームウェア、デバイス制御など。
  • 拡張スクリプト: アプリケーションの設定、ワークフローの自動化、機能追加など(Wireshark, Neovimなど)。
  • Web開発: 一部のフレームワークやサーバー(OpenRestyなど)。

この記事では、Luaを単体で使用する場合の基本的な書き方に焦点を当てますが、これらの応用分野で Luaがどのように活用されているかを頭の片隅に置いておくと、学習のモチベーションにも繋がるでしょう。

2. 開発環境の構築

Luaコードを実行するには、Luaインタプリタが必要です。最も一般的な方法は、公式のインタプリタをインストールすることです。

2.1 Luaインタプリタのインストール

Windows

公式サイト (https://www.lua.org/download.html) からソースコードをダウンロードしてビルドすることも可能ですが、より簡単な方法として、既にビルドされたバイナリを利用する方法があります。

  • Lua for Windows: (https://github.com/rjpcomputing/luaforwindows) Lua本体に加え、SciTEエディタや様々なライブラリが含まれています。初心者にはおすすめです。
  • scoop または Chocolatey: パッケージマネージャを使っている場合は、scoop install lua または choco install lua でインストールできます。

インストール後、コマンドプロンプトを開き、lua -v と入力してバージョン情報が表示されれば成功です。

macOS

macOSでは、Homebrewパッケージマネージャを使うのが最も簡単です。

  1. ターミナルを開きます。
  2. Homebrewがインストールされていない場合は、公式サイト (https://brew.sh/) の手順に従ってインストールします。
  3. brew install lua を実行します。

インストール後、ターミナルで lua -v と入力してバージョン情報が表示されれば成功です。

Linux

多くのLinuxディストリビューションでは、パッケージマネージャを通じてLuaをインストールできます。

  • Debian/Ubuntu系: sudo apt update && sudo apt install lua5.4 (バージョンは環境によって異なる場合があります)
  • Fedora/CentOS/RHEL系: sudo dnf install lua または sudo yum install lua

インストール後、ターミナルで lua -v と入力してバージョン情報が表示されれば成功です。

2.2 Luaインタプリタの実行方法

Luaインタプリタには、主に2つの実行方法があります。

  1. 対話モード (Interactive Mode):
    コマンドラインで lua と入力してEnterを押すと、> または Lua 5.x.x > のようなプロンプトが表示されます。ここでLuaコードを直接入力して実行できます。簡単なコードのテストや、APIの動作確認に便利です。終了するには os.exit() またはCtrl+Cを入力します。

    “`bash
    $ lua
    Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio

    print(“Hello, Lua!”)
    Hello, Lua!
    a = 10
    b = 20
    print(a + b)
    30
    os.exit()
    $
    “`

  2. スクリプトファイルの実行:
    テキストエディタでLuaコードを記述したファイル(.lua 拡張子を付けるのが一般的)を作成し、コマンドラインで lua your_script_name.lua のように指定して実行します。

    bash
    $ lua your_script.lua

2.3 テキストエディタ/IDE

Luaコードを書くためには、シンプルなテキストエディタで十分ですが、シンタックスハイライトや補完機能があると開発効率が上がります。

  • Visual Studio Code (VS Code): 多くのプログラマーに人気のエディタです。Luaの拡張機能(例: Lua, Lua Helperなど)が多数提供されています。
  • Sublime Text, Atom, Notepad++: これらも広く使われており、Luaのシンタックスハイライトをサポートしています。
  • SciTE: Lua for Windowsに含まれている軽量なエディタです。Luaの作者自身も開発に関わっています。

お好みのエディタを用意しましょう。

3. 最初のプログラム:Hello, World!

どんなプログラミング言語でも最初のステップは「Hello, World!」の表示です。

お好みのテキストエディタを開き、以下のコードを記述します。

lua
-- これは最初のLuaプログラムです
print("Hello, World!")

このファイルを hello.lua という名前で保存します。

次に、コマンドプロンプトやターミナルを開き、保存したディレクトリに移動して、以下のコマンドを実行します。

bash
$ lua hello.lua

実行結果として、コンソールに Hello, World! と表示されるはずです。

コードの説明:

  • -- これは最初のLuaプログラムです: --- は単一行コメントです。この行はインタプリタによって無視されます。
  • print("Hello, World!"): print はLuaの組み込み関数で、引数として与えられた値をコンソールに出力します。この場合、文字列 "Hello, World!" が出力されます。

非常にシンプルですね。Luaの基本的な書き方を理解するための最初のステップです。

4. 基本文法

Luaの構文は非常にシンプルで、他の多くのプログラミング言語と比べて覚えるべき規則が少ないのが特徴です。

4.1 コメントアウト

コードの一部を無効化したり、説明を記述したりするためにコメントを使用します。

  • 単一行コメント: -- から行末までがコメントになります。

    lua
    -- これは単一行コメントです
    x = 10 -- ここから行末までもコメントです

  • 複数行コメント: --[[ から --]] までがコメントになります。

    “`lua
    –[[
    これは
    複数行コメントです。
    コードブロック全体をコメントアウトするのに便利です。
    –]]

    print(“この行は実行されます”)
    “`

    複数行コメントは、最初の [[ の直後に = を連続して挿入することで、ネストされたコメントを無効化する機能もあります。例えば --[=[ ... ]=] のように書くことで、コメント内の --[[ ... ]] を有効なコメントとして扱うことができます。通常は --[[ ... ]] の形式で十分です。

4.2 予約語 (キーワード)

Luaには以下の予約語(キーワード)があり、これらを変数名などに使用することはできません。

and break do else elseif end
false for function goto if in
local nil not or repeat return
then true until while

これらのキーワードは、Luaの特定の構文(条件分岐、ループ、関数定義など)で使用されます。

4.3 大文字・小文字の区別

Luaは大文字・小文字を区別します。例えば、variable, Variable, VARIABLE はそれぞれ異なる変数として扱われます。キーワードも小文字で正確に記述する必要があります (if は予約語ですが IF は普通の識別子として使えますが、混乱を避けるために避けましょう)。

4.4 ステートメント区切り

C言語やJavaのように、ステートメントの終わりにセミコロン ; を付ける必要はありません。各行が1つのステートメントであることが一般的です。ただし、1行に複数のステートメントを記述する場合は、セミコロンで区切る必要があります。

lua
a = 10 -- セミコロンは不要
b = 20; c = 30 -- 1行に複数書く場合は必要

可読性の観点から、1行に1つのステートメントを記述し、セミコロンを省略するのがLuaの一般的なスタイルです。

5. データ型

Luaは動的型付け言語です。変数は特定の型に固定されず、代入される値によって型が決まります。Luaには以下の8つの基本的なデータ型があります。

5.1 nil

nil は「値がない」ことを表す型です。グローバル変数のデフォルト値は nil です。変数を nil に代入することで、その変数を削除したのと同じ効果を持ちます(ただし、ローカル変数の場合はスコープを抜けるまで存在し続けます)。

lua
local my_var = 10
print(my_var) -- 出力: 10
my_var = nil
print(my_var) -- 出力: nil

5.2 boolean

boolean は真偽値を表す型で、truefalse の2つの値があります。

lua
local is_active = true
local is_finished = false
print(is_active) -- 出力: true
print(is_finished) -- 出力: false

Luaでは、falsenil だけが偽と評価されます。その他の値(true, 数値の 0, 空文字列 "", 空テーブル {} など)はすべて真と評価されます。これは他の言語(特にC言語など)とは異なる点なので注意が必要です。

“`lua
if 0 then
print(“0 は真と評価されます”) — これが出力される
end

if “” then
print(“空文字列は真と評価されます”) — これが出力される
end

if nil then
— 何も出力されない
else
print(“nil は偽と評価されます”) — これが出力される
end
“`

5.3 number

number は数値を表す型です。Lua 5.3以降では、整数型 (integer) と浮動小数点数型 (float) が区別されますが、特に意識せずとも自動的に変換されます。標準のビルドでは64ビットの整数と浮動小数点数が使われることが一般的です。

“`lua
local integer_num = 123
local float_num = 45.67
local scientific_num = 1.23e5 — 123000.0

print(integer_num)
print(float_num)
print(scientific_num)

print(type(integer_num)) — 出力例: number
print(type(float_num)) — 出力例: number
``type()` 関数は変数の型を文字列で返します。

5.4 string

string は文字列を表す型です。シングルクォート (')、ダブルクォート (")、または複数行文字列リテラル ([[...]] または [=[...]=]) で囲んで表現します。

“`lua
local str1 = “これはダブルクォートの文字列です”
local str2 = ‘これはシングルクォートの文字列です’
local str3 = [[
これは
複数行の
文字列です。
改行やクォートをそのまま含めます。 ” ‘
]]

print(str1)
print(str2)
print(str3)
“`

文字列はイミュータブル(不変)です。文字列操作関数は新しい文字列を返します。

5.5 function

function は関数を表す型です。関数は第一級オブジェクトとして扱われます。つまり、変数に代入したり、関数の引数として渡したり、戻り値として返したりすることができます。

“`lua
local my_func = function(a, b)
return a + b
end

print(type(my_func)) — 出力: function
print(my_func(10, 20)) — 出力: 30
“`

関数については後ほど詳しく解説します。

5.6 userdata

userdata は、C/C++コードによって作成された任意のデータをLuaから扱うための型です。ポインタや構造体などを表現するのに使われます。Luaコード内では直接作成したり操作したりすることはほとんどなく、C/C++拡張ライブラリを通じて利用されます。

5.7 thread

thread は、コルーチン(Coroutines)を表す型です。コルーチンは非同期処理やイテレータなどを実装するのに役立ちます。スレッドとは異なり、協調的マルチタスクを実現するための仕組みです。コルーチンについても後ほど簡単に触れます。

5.8 table

table は、Luaで唯一の構造化データ型であり、最も重要なデータ型です。配列、ハッシュマップ(辞書)、オブジェクトなどをすべてテーブルとして表現します。キーと値のペアの集まりで、キーには nilNaN (Not a Number) を除く任意の型の値(数値、文字列、他のテーブルなど)を使用できます。値には任意の型の値を使用できます。

テーブルは {} を使って作成します。

“`lua
local empty_table = {} — 空のテーブル
local array_like_table = { 10, 20, 30 } — 配列のように使う(インデックスは1から始まる!)
local dict_like_table = { name = “Alice”, age = 30 } — キーと値のペア
local mixed_table = { 1, “apple”, key = “value”, [true] = 123 } — 混在も可能

print(type(empty_table)) — 出力: table
“`

テーブルについては、その重要性から独立したセクションで詳しく解説します。

6. 変数

Luaでは、変数を使用する前に明示的な宣言(Javaの int x; のようなもの)は不要です。ただし、変数のスコープを制御するために local キーワードを使うのが非常に重要です。

6.1 変数の宣言と代入

変数への最初の代入が行われると、その変数が作成されます。

“`lua
my_variable = 10 — グローバル変数
local another_variable = “hello” — ローカル変数

print(my_variable)
print(another_variable)
“`

6.2 ローカル変数とグローバル変数

  • ローカル変数 (Local Variables): local キーワードを使って宣言された変数です。宣言されたブロック(do...end, if...end, for...end, while...end, function...end など)とその内部スコープでのみ有効です。推奨される変数の使い方です。

    “`lua
    local x = 10

    if true then
    local y = 20 — yはifブロック内でのみ有効
    print(x) — xはアクセス可能
    print(y)
    end

    print(x)
    — print(y) — エラー: yはスコープ外
    “`

  • グローバル変数 (Global Variables): local キーワードを使わずに代入された変数です。プログラム全体からアクセス可能ですが、意図しない名前の衝突や依存関係を生みやすいため、多用は避けるべきです。グローバル変数は、存在しない場合にアクセスすると nil を返します。

    “`lua
    — local を付けずに代入するとグローバル変数になる
    global_var = “I am global”

    function print_global()
    print(global_var) — 関数内からもアクセス可能
    end

    print_global()

    — 存在しないグローバル変数にアクセスすると nil になる
    print(non_existent_var) — 出力: nil

    — グローバル変数を削除するには nil を代入する
    global_var = nil
    print(global_var) — 出力: nil
    “`

重要: Luaでは、local を付けずに代入すると自動的にグローバル変数になります。これは他の言語と異なる挙動であり、意図せずグローバル変数を作成してしまうバグの原因になりやすいです。基本的にすべての変数宣言には local を付けることを強く推奨します。

6.3 複数の変数への同時代入

Luaでは、複数の変数に同時に値を代入することができます。

“`lua
a, b = 10, 20
print(a, b) — 出力: 10 20

— 値の数を変数の数より少なくしてもエラーにはならない
x, y, z = 1, 2
print(x, y, z) — 出力: 1 2 nil (不足分は nil になる)

— 値の数を変数の数より多くしてもエラーにはならない
p, q = 100, 200, 300
print(p, q) — 出力: 100 200 (余分な値は無視される)

— 値の交換によく使われる
local var1 = 1
local var2 = 2
var1, var2 = var2, var1
print(var1, var2) — 出力: 2 1
“`

関数の戻り値が複数ある場合にも、この同時代入が非常に便利です。

“`lua
local function get_coords()
return 100, 200
end

local x, y = get_coords()
print(x, y) — 出力: 100 200
“`

7. 演算子

Luaには、数値、文字列、論理値、テーブルなどを操作するための様々な演算子があります。

7.1 算術演算子

数値に対して使用します。

演算子 説明 結果
+ 加算 10 + 5 15
- 減算 10 - 5 5
* 乗算 10 * 5 50
/ 除算 10 / 5 2.0
% 剰余 10 % 3 1
^ べき乗 2 ^ 3 8
// 切り捨て除算 10 // 3 3

注意: / は常に浮動小数点数を返します。整数除算を行いたい場合は // を使用します。

7.2 関係演算子

2つの値を比較し、真偽値 (true または false) を返します。

演算子 説明 結果
== 等しい 1 == 1 true
~= 等しくない 1 ~= 2 true
< より小さい 1 < 2 true
> より大きい 2 > 1 true
<= より小さいまたは等しい 1 <= 1 true
>= より大きいまたは等しい 2 >= 1 true

異なる型の値を比較する場合、Luaは可能な場合は型変換を試みます(例: 数値と数値形式の文字列)。しかし、通常は同じ型の値同士を比較するのが安全です。テーブル、userdata、threadは参照で比較されます(同じオブジェクトを参照しているか)。文字列は辞書順で比較されます。

7.3 論理演算子

真偽値に対して使用します。Luaの論理演算子は、他の言語と少し異なる振る舞いをします。

  • and: 左辺が真と評価できれば右辺の値を、そうでなければ左辺の値を返します。
  • or: 左辺が真と評価できれば左辺の値を、そうでなければ右辺の値を返します。
  • not: 否定。常に true または false を返します。

Luaでは falsenil だけが偽と評価されることを思い出してください。

“`lua
print(true and 10) — 出力: 10
print(false and 10) — 出力: false
print(nil and 10) — 出力: nil

print(true or 10) — 出力: true
print(false or 10) — 出力: 10
print(nil or 10) — 出力: 10

print(not true) — 出力: false
print(not false) — 出力: true
print(not nil) — 出力: true
print(not 0) — 出力: false (0 は真と評価されるため)
``andor演算子はショートサーキット評価を行います。例えばa and bでは、aが偽ならb` は評価されません。

7.4 文字列連結演算子

.. 演算子を使用して文字列を連結します。数値が文字列と連結される場合、数値は自動的に文字列に変換されます。

“`lua
local str1 = “Hello”
local str2 = “World”
local greeting = str1 .. ” ” .. str2 .. “!”
print(greeting) — 出力: Hello World!

local num = 123
local result = “Number: ” .. num
print(result) — 出力: Number: 123
“`

7.5 長さ演算子

# 演算子は、文字列の長さ、またはシーケンスとして使われているテーブルのサイズ(1から始まる連続した正の整数のキーの数)を返します。

“`lua
local my_string = “Lua”
print(#my_string) — 出力: 3

local my_array = {10, 20, 30, 40}
print(#my_array) — 出力: 4

local mixed_table = { 1, 2, a = 10, b = 20 }
print(#mixed_table) — 出力例: 2 (1, 2のシーケンス部分)

local sparse_array = { [1] = 10, [3] = 30, [5] = 50 }
print(#sparse_array) — 出力例: 0, 1, 3, または 5 (Lua 5.2以降では、シーケンスの終端をヒューリスティックに判断するため、必ずしも期待通りにならない場合がある)
``
テーブルの
#` 演算子は、テーブルが厳密なシーケンスでない場合に注意が必要です。

7.6 演算子の優先順位

演算子には優先順位があり、式が評価される順序を決定します。高いものから低いものへ:

  1. ^ (べき乗)
  2. not, #, - (単項マイナス), ~ (ビット否定 – Lua 5.3+)
  3. *, /, %, //
  4. +, - (二項マイナス)
  5. ..
  6. <, >, <=, >=, ==, ~=
  7. and
  8. or

括弧 () を使用して、優先順位を明示的に指定したり、デフォルトの優先順位を変更したりできます。

lua
print(2 + 3 * 4) -- 出力: 14 (乗算が先)
print((2 + 3) * 4) -- 出力: 20 (括弧内が先)

8. 制御構造

プログラムの実行フローを制御するための構文です。

8.1 条件分岐

特定の条件に基づいてコードのブロックを実行するかどうかを決定します。

  • if 文:

    “`lua
    local score = 85

    if score >= 90 then
    print(“Excellent!”)
    end
    “`

  • if-else 文:

    “`lua
    local is_student = true

    if is_student then
    print(“You are a student.”)
    else
    print(“You are not a student.”)
    end
    “`

  • if-elseif-else 文: 複数の条件を順番にチェックします。

    “`lua
    local grade = 75

    if grade >= 90 then
    print(“Grade: A”)
    elseif grade >= 80 then
    print(“Grade: B”)
    elseif grade >= 70 then
    print(“Grade: C”)
    else
    print(“Grade: D or F”)
    end
    “`

すべての条件分岐ブロックは end で閉じます。Luaでは、他の言語のような {} の代わりに end を使ってブロックを定義します。

8.2 繰り返し (ループ)

コードブロックを繰り返し実行します。

  • while ループ: 条件が真の間、ブロックを繰り返し実行します。

    lua
    local count = 1
    while count <= 5 do
    print("Count: " .. count)
    count = count + 1
    end
    -- 出力:
    -- Count: 1
    -- Count: 2
    -- Count: 3
    -- Count: 4
    -- Count: 5

    条件チェックはループの開始前に行われます。条件が最初から偽の場合、ブロックは一度も実行されません。

  • repeat...until ループ: ブロックを一度実行した後、条件が真になるまで繰り返し実行します。

    lua
    local count = 1
    repeat
    print("Repeat count: " .. count)
    count = count + 1
    until count > 5
    -- 出力:
    -- Repeat count: 1
    -- Repeat count: 2
    -- Repeat count: 3
    -- Repeat count: 4
    -- Repeat count: 5

    条件チェックはループの終了後に行われるため、ブロックは少なくとも一度は実行されます。

  • for ループ (数値 for): 指定された範囲で変数を変化させながら繰り返し実行します。

    “`lua
    — 1から5まで1ずつ増加
    for i = 1, 5 do
    print(“Numerical for (up): ” .. i)
    end
    — 出力: 1, 2, 3, 4, 5

    — 5から1まで1ずつ減少 (ステップ値 -1 を指定)
    for j = 5, 1, -1 do
    print(“Numerical for (down): ” .. j)
    end
    — 出力: 5, 4, 3, 2, 1

    — ステップ値を省略した場合、デフォルトは1
    for k = 1, 3 do
    print(“Numerical for (default step): ” .. k)
    end
    — 出力: 1, 2, 3
    ``
    数値 for ループのカウンタ変数(上記の
    i,j,k`)は、ループのローカル変数として扱われます。ループが終了すると、これらの変数はスコープ外になります。

  • for ループ (汎用 for): イテレータ関数を使って繰り返し実行します。テーブルの要素を順番に処理するのに非常によく使われます。

    “`lua
    local my_table = { name = “Bob”, age = 25, city = “Tokyo” }

    — pairs: テーブルのすべてのキーと値のペアを順不同で取得
    for key, value in pairs(my_table) do
    print(key .. “: ” .. value)
    end
    — 出力例 (順不同):
    — name: Bob
    — age: 25
    — city: Tokyo

    local my_array = {“apple”, “banana”, “cherry”}

    — ipairs: テーブルの数値インデックス(1から始まる連続した整数)を順番に取得
    for index, value in ipairs(my_array) do
    print(index .. “: ” .. value)
    end
    — 出力:
    — 1: apple
    — 2: banana
    — 3: cherry
    ``pairsはテーブルのすべてのキー/値ペアを列挙し、ipairs` は1から始まる連続した数値キーを持つ要素のみを列挙します。テーブルのイテレーションにはこれらを使い分けることが重要です。

8.3 制御フローの中断

  • break: 現在実行中の for, while, repeat ループを即座に終了し、ループの直後のコードに制御を移します。

    lua
    local i = 1
    while true do
    print("Loop iteration: " .. i)
    if i >= 3 then
    break -- iが3以上になったらループを抜ける
    end
    i = i + 1
    end
    print("Loop finished.")
    -- 出力:
    -- Loop iteration: 1
    -- Loop iteration: 2
    -- Loop iteration: 3
    -- Loop finished.

  • return: 関数から値を返し、関数の実行を終了します。スクリプトのトップレベルで使用すると、スクリプトの実行を終了します。

    “`lua
    function find_item(items, item)
    for i, v in ipairs(items) do
    if v == item then
    return i — アイテムが見つかったらインデックスを返して終了
    end
    end
    return nil — 見つからなければ nil を返す
    end

    local list = {“A”, “B”, “C”}
    print(find_item(list, “B”)) — 出力: 2
    print(find_item(list, “D”)) — 出力: nil
    “`

  • goto: Lua 5.2で追加された機能で、ラベル (::label::) を指定してプログラムの実行位置をジャンプさせます。多用するとコードが読みにくくなるため、注意して使用する必要があります。主に特定のループから多重に抜け出したい場合などに限定的に使われます。

    “`lua
    function search_matrix(matrix, value)
    for i, row in ipairs(matrix) do
    for j, element in ipairs(row) do
    if element == value then
    print(“Found at:”, i, j)
    goto found — 見つかったらラベルへジャンプ
    end
    end
    end
    print(“Not found.”)
    return

    ::found:: -- ジャンプ先ラベル
    -- ここにジャンプしてくる
    

    end

    local matrix = { {1, 2}, {3, 4}, {5, 6} }
    search_matrix(matrix, 4) — 出力: Found at: 2 2
    search_matrix(matrix, 7) — 出力: Not found.
    ``goto` はローカル変数のスコープを飛び越えたり、関数定義の内部から外部へジャンプしたりすることはできません。

9. 関数

関数は特定の処理をまとめたブロックであり、繰り返し利用したり、コードを整理したりするのに役立ちます。Luaでは関数は第一級オブジェクトです。

9.1 関数の定義と呼び出し

関数は function キーワードを使って定義します。

“`lua
— 関数の定義
function greet(name)
print(“Hello, ” .. name .. “!”)
end

— 関数の呼び出し
greet(“Alice”) — 出力: Hello, Alice!
greet(“Bob”) — 出力: Hello, Bob!
``
引数がない関数は括弧
()` を付けて呼び出すのが一般的ですが、引数がなく、かつ関数呼び出しが単一の文字列リテラルまたはテーブルコンストラクタである場合は、括弧を省略できます(これは特殊なケースです)。

lua
print("Hello") -- print("Hello") と同じ
local t = {}
local func = function() print("called") end
func{} -- func({}) と同じ
func"" -- func("") と同じ

9.2 戻り値

関数は return キーワードを使って値を返すことができます。Luaの関数は複数の値を返すことができます。

“`lua
function add(a, b)
return a + b
end

local sum = add(5, 3)
print(sum) — 出力: 8

function get_info()
return “Alice”, 30 — 複数の値を返す
end

local name, age = get_info() — 複数の変数で受け取る
print(name, age) — 出力: Alice 30

local info = {get_info()} — テーブルで受け取ることも可能
print(info[1], info[2]) — 出力: Alice 30
``returnステートメントがない場合、またはreturnの後ろに何も書かれていない場合、関数はnil` を返します。

9.3 可変長引数

関数は ... を引数リストの最後に記述することで、可変長の引数を受け取ることができます。これらの引数は、関数内で ... としてアクセスできます。select('#', ...) で引数の個数を取得できます。

“`lua
function print_all(…)
local args = {…} — 可変長引数をテーブルとして受け取る
print(“Number of arguments:”, select(‘#’, …)) — 引数の数を取得
for i, v in ipairs(args) do
print(“Arg ” .. i .. “: ” .. tostring(v)) — tostringで文字列に変換して表示
end
end

print_all(10, “hello”, true, {a=1})
— 出力例:
— Number of arguments: 4
— Arg 1: 10
— Arg 2: hello
— Arg 3: true
— Arg 4: table: 0x… (テーブルのメモリアドレス)
“`

9.4 ローカル関数

local キーワードを使ってローカル関数を定義することができます。ローカル関数は定義されたスコープ内でのみ有効です。推奨される関数の定義方法です。

“`lua
local function my_local_func()
print(“This is a local function.”)
end

my_local_func() — 定義されたスコープ内なので呼び出せる
— other_scope()
— my_local_func() — エラー (スコープ外の場合)

function global_func()
print(“This is a global function.”)
end

global_func() — どこからでも呼び出せる
“`
ローカル関数を定義する際に、関数内で自分自身を再帰呼び出ししたい場合は、以下の形式で宣言すると、関数名がローカル変数として先に作成されるため、定義完了前に呼び出してもエラーになりません。

“`lua
local recursive_func
recursive_func = function(n)
if n > 0 then
print(n)
recursive_func(n – 1) — 定義完了前に呼び出せる
end
end

recursive_func(3)
``
あるいは、関数の定義を
local function func_name(…) … end` の形式で行う場合も、関数名が事前にローカル変数として作成されるため再帰呼び出しは問題ありません。

9.5 クロージャ

Luaの関数はクロージャをサポートしています。クロージャとは、関数が定義された環境(ローカル変数など)を「記憶」している関数のことです。

“`lua
function create_counter()
local count = 0 — この変数は create_counter が終了しても保持される
return function() — 匿名関数(クロージャ)を返す
count = count + 1
return count
end
end

local counter1 = create_counter() — 最初のカウンタオブジェクトを作成
local counter2 = create_counter() — 2番目のカウンタオブジェクトを作成

print(counter1()) — 出力: 1
print(counter1()) — 出力: 2
print(counter2()) — 出力: 1 (counter1とは別の状態を持つ)
print(counter1()) — 出力: 3
``create_counter関数が返した匿名関数は、それぞれ独自のcount` 変数を記憶しています。これにより、オブジェクト指向のような状態を持った処理を実現できます。

10. テーブル (Tables) – Luaの中心

テーブルはLuaで最も重要なデータ型です。配列、辞書、オブジェクト指向におけるインスタンスなど、様々なデータ構造を表現できます。テーブルは参照型です。

10.1 テーブルの作成

テーブルは {} を使って作成します。

“`lua
local t1 = {} — 空のテーブル

— 要素を初期化しながら作成
local t2 = {
“apple”, — 数値キー(1から始まる)
“banana”,
name = “Alice”, — 文字列キー
age = 25,
[true] = “yes”, — boolean型のキー
[100] = “hundred” — 数値キー(連続していなくてもOK)
}
``
テーブル作成時の
{}` 内の要素の書き方にはいくつかバリエーションがあります。

  • value: 数値キー(1, 2, 3…)として順に追加されます。
  • key = value: キーが有効なLua識別子の場合に使えます(文字列キー)。
  • [key] = value: キーが任意の式の評価結果になる場合に使えます。

lua
local t3 = {
"first", -- インデックス 1
"second", -- インデックス 2
["third"] = 3, -- キー "third"
fourth = 4, -- キー "fourth" (文字列キーの省略記法)
[10] = "tenth" -- インデックス 10
}
-- t3 は { [1]="first", [2]="second", ["third"]=3, ["fourth"]=4, [10]="tenth" } のようなもの

10.2 要素へのアクセス

テーブルの要素には、キーを使ってアクセスします。

  • 角括弧 [] によるアクセス: キーを式で指定します。

    “`lua
    local my_table = { name = “Bob”, age = 30, items = {“book”, “pen”} }

    print(my_table[“name”]) — 出力: Bob
    print(my_table[“age”]) — 出力: 30
    print(my_table[“items”][1]) — 出力: book (ネストされたテーブルへのアクセス)

    local key_name = “name”
    print(my_table[key_name]) — 出力: Bob (変数でキーを指定)
    “`

  • ドット . によるアクセス: キーが有効なLua識別子(変数名などに使える名前)である場合にのみ使えます。これは糖衣構文(シンタックスシュガー)であり、table.keytable["key"] と等価です。

    “`lua
    local my_table = { name = “Alice”, age = 25 }

    print(my_table.name) — 出力: Alice
    print(my_table.age) — 出力: 25

    — my_table.my-key = “value” — エラー: my-key は有効な識別子ではない
    — my_table[“my-key”] = “value” — OK
    “`

存在しないキーにアクセスした場合、戻り値は nil になります。これは新しい要素を追加したり、既存の要素を削除したりするのに利用できます。

“`lua
local person = { name = “Charlie” }
print(person.age) — 出力: nil

— 要素の追加/更新
person.age = 35
person[“city”] = “London”
print(person.age, person.city) — 出力: 35 London

— 要素の削除 (nil を代入する)
person.age = nil
print(person.age) — 出力: nil
“`

10.3 配列としての利用 (1から始まるインデックス)

Luaのテーブルを配列として使う場合、インデックスは1から始まります。これは多くの他の言語(0から始まる)と異なる重要な点です。

“`lua
local fruits = {“apple”, “banana”, “cherry”}

print(fruits[1]) — 出力: apple
print(fruits[2]) — 出力: banana
print(fruits[3]) — 出力: cherry
print(fruits[0]) — 出力: nil (インデックス0の要素は存在しない)
print(fruits[4]) — 出力: nil (インデックス4の要素は存在しない)

— 要素の追加 (末尾に追加するには table.insert を使うのが一般的)
fruits[4] = “date”
print(fruits[4]) — 出力: date
``
数値インデックスを連続して使用している場合、
#` 演算子でサイズを取得できます(ただし、厳密なシーケンスでない場合は注意が必要です)。

10.4 ハッシュマップ/辞書としての利用

文字列や他の型の値をキーとして使用することで、テーブルをハッシュマップや辞書として利用できます。

“`lua
local config = {
host = “localhost”,
port = 8080,
enabled = true,
options = { timeout = 10 } — テーブルを値として持つことも可能
}

print(config.host) — 出力: localhost
print(config[“port”]) — 出力: 8080
print(config.options.timeout) — 出力: 10
“`

キーには nilNaN (Not a Number) を除く任意の型の値を使用できます。

“`lua
local mixed_keys = {
[“string_key”] = 123,
[10] = “ten”,
[true] = “boolean_key”,
[{}] = “table_as_key” — テーブルもキーにできる (参照で区別される)
}

print(mixed_keys.string_key) — 出力: 123 (ドット記法は文字列キーの糖衣構文)
print(mixed_keys[“string_key”]) — 出力: 123
print(mixed_keys[10]) — 出力: ten
print(mixed_keys[true]) — 出力: boolean_key

local t = {}
mixed_keys[t] = “another_table_as_key”
print(mixed_keys[t]) — 出力: another_table_as_key

print(mixed_keys[{}]) — 出力: nil ({} は新しいテーブルを作成するので、上の {} とは別のオブジェクト)
“`

10.5 テーブルのネスティング

テーブルは他のテーブルを値として持つことができます。これにより、複雑なデータ構造を構築できます。

“`lua
local company = {
name = “Tech Corp”,
location = {
city = “San Francisco”,
country = “USA”
},
employees = {
{ name = “Alice”, id = 1 },
{ name = “Bob”, id = 2 }
}
}

print(company.name)
print(company.location.city)
print(company.employees[1].name)
print(company.employees[2].id)
“`

10.6 テーブル操作の組み込み関数

標準ライブラリ table には、テーブル(特に配列のように使われているテーブル)を操作する便利な関数がいくつか用意されています。

  • table.insert(table, [pos, ] value): テーブルに要素を挿入します。pos を省略すると末尾に追加します。

    lua
    local list = {"a", "b", "c"}
    table.insert(list, "d") -- {"a", "b", "c", "d"} になる
    table.insert(list, 2, "X") -- {"a", "X", "b", "c", "d"} になる
    print(list[1], list[2], list[3], list[4], list[5])
    -- 出力: a X b c d

  • table.remove(table, [pos]): テーブルから要素を削除し、削除された要素を返します。pos を省略すると末尾から削除します。

    “`lua
    local list = {“a”, “X”, “b”, “c”, “d”}
    local removed = table.remove(list, 2) — インデックス2の要素 (“X”) を削除
    print(removed) — 出力: X
    print(#list) — 出力: 4
    print(list[1], list[2], list[3], list[4])
    — 出力: a b c d

    removed = table.remove(list) — 末尾の要素 (“d”) を削除
    print(removed) — 出力: d
    print(#list) — 出力: 3
    print(list[1], list[2], list[3])
    — 出力: a b c
    “`

  • table.sort(table, [comp]): テーブルをソートします。comp 関数(オプション)を指定すると、その比較関数に従ってソートされます。comp(a, b)ab より前にあるべき場合に true を返す関数です。

    “`lua
    local numbers = {3, 1, 4, 1, 5, 9, 2}
    table.sort(numbers) — デフォルトは昇順ソート
    print(table.concat(numbers, “, “)) — 出力: 1, 1, 2, 3, 4, 5, 9

    local strings = {“banana”, “apple”, “cherry”}
    table.sort(strings)
    print(table.concat(strings, “, “)) — 出力: apple, banana, cherry

    — 降順ソートの例
    local function descending_comp(a, b)
    return a > b — aがbより大きい場合にtrueを返す
    end

    local numbers_desc = {3, 1, 4, 1, 5}
    table.sort(numbers_desc, descending_comp)
    print(table.concat(numbers_desc, “, “)) — 出力: 5, 4, 3, 1, 1
    ``table.concat(table, sep, i, j)` はテーブルの要素を文字列として連結する関数です。

10.7 テーブルのサイズ

前述の通り、#table 演算子は、テーブルがシーケンスとして使われている場合の要素数を返します。これは、1から始まる連続した正の整数キーを持つ要素の数をカウントします。

非シーケンスなテーブル(文字列キーや飛び飛びの数値キーのみを持つテーブル)に対して # 演算子を使用しても、通常は0を返します。テーブル内の要素の正確な総数を取得する標準的な簡単な方法はありません。すべてのキー/値ペアを数えるには pairs ループを使う必要があります。

“`lua
local t1 = {10, 20, 30}
print(#t1) — 出力: 3

local t2 = {a=”apple”, b=”banana”}
print(#t2) — 出力: 0

local t3 = {[1]=10, [3]=30}
print(#t3) — 出力例: 0, 1, または 3 (Luaのバージョンや実装による。シーケンスの終端判定はヒューリスティック)

local t4 = {10, 20, [10] = 100}
print(#t4) — 出力例: 2

— 全要素数を数える (汎用的な方法)
local function count_elements(t)
local count = 0
for _, _ in pairs(t) do
count = count + 1
end
return count
end

local t5 = {1, 2, a=”hello”, b=”world”, [100]=”test”}
print(count_elements(t5)) — 出力: 5
``#` 演算子は、テーブルが純粋な配列やシーケンスの場合には便利ですが、それ以外の場合は注意が必要です。

11. モジュールとrequire

Luaでは、コードを複数のファイルに分割し、必要に応じて読み込むことができます。これはモジュールシステムを通じて行われます。

11.1 require 関数によるモジュールのロード

require(module_name) 関数は、指定されたモジュールを検索してロードし、そのモジュールが返す値を返します。モジュールは一度ロードされると、その結果がキャッシュされ、2回目以降の require はキャッシュされた値を返します。

例えば、my_module.lua というファイルに以下のコードがあるとします。

“`lua
— my_module.lua
print(“my_module.lua がロードされました”)

local M = {} — モジュールとして返すテーブルを作成

M.greet = function(name)
print(“Hello from module, ” .. name .. “!”)
end

M.version = “1.0”

local private_var = “This is private” — この変数はモジュールの外からはアクセスできない

return M — モジュールが返す値
“`

別のファイル(例: main.lua)からこのモジュールを利用するには、require を使います。

“`lua
— main.lua
local my_module = require(“my_module”) — ファイル拡張子 .lua は省略

my_module.greet(“Charlie”) — ロードしたモジュールの関数を呼び出す
print(“Module version:”, my_module.version)

— print(my_module.private_var) — エラーまたは nil (アクセスできない)

local my_module_again = require(“my_module”) — 2回目はキャッシュされたものが返される
— “my_module.lua がロードされました” は最初の一度だけ表示される
``requireは、デフォルトでいくつかのパス(パッケージパス)を順番に検索してモジュールファイルを見つけます。パッケージパスはpackage.path` 変数で確認できます。

11.2 独自のモジュールの作成方法

簡単なモジュールを作成するには、以下のパターンが一般的です。

  1. ローカル変数としてモジュールを表すテーブルを作成する(例: local M = {})。
  2. モジュールとして公開したい関数や値をそのテーブルに追加する(例: M.public_func = ..., M.public_var = ...)。
  3. ファイルの最後にそのテーブルを return する。

“`lua
— my_utils.lua
local utils = {} — モジュールテーブル

function utils.add(a, b) — テーブルに関数を追加
return a + b
end

function utils.subtract(a, b)
return a – b
end

local private_helper = function(x) — 非公開関数
— 内部処理
end

return utils — モジュールテーブルを返す
“`

このモジュールは以下のように利用できます。

“`lua
local my_utils = require(“my_utils”)

print(my_utils.add(10, 5)) — 出力: 15
print(my_utils.subtract(10, 5)) — 出力: 5

— my_utils.private_helper() — エラー (非公開なのでアクセスできない)
“`

このモジュールパターンにより、グローバルスコープを汚染することなく、コードを構造化して再利用可能な部品に分割できます。

12. エラーハンドリング

プログラムの実行中に発生するエラー(実行時エラー)を適切に処理することは重要です。

12.1 pcall (protected call)

pcall(func, arg1, arg2, ...) 関数は、指定された関数 func を保護されたモードで実行します。func の実行中にエラーが発生しても、プログラムはクラッシュせず、pcall がエラーを捕捉します。

pcall は2つの値を返します。

  1. 実行が成功したか (true または false)
  2. 成功した場合は関数の戻り値、失敗した場合はエラーメッセージ

“`lua
— エラーが発生する関数
local function cause_error()
print(“Calling a function that will error…”)
error(“Something went wrong!”) — エラーを発生させる組み込み関数
print(“This line will not be reached.”)
end

— pcall を使って呼び出す
local success, result = pcall(cause_error)

if success then
print(“Function executed successfully. Result:”, result)
else
print(“Function failed. Error message:”, result) — result にはエラーメッセージが入る
end
— 出力例:
— Calling a function that will error…
— Function failed. Error message: Something went wrong!
“`

エラーメッセージには、どのファイル、どの行でエラーが発生したかなどの情報が含まれていることが一般的です。

12.2 xpcall (extended protected call)

xpcall(func, msgh, arg1, arg2, ...)pcall と似ていますが、エラーが発生した場合にエラーハンドラ関数 msgh を呼び出すことができる点が異なります。msgh はエラーメッセージを引数として受け取り、その戻り値が xpcall の第2戻り値となります。これは、エラー発生時のスタックトレースなどの詳細な情報を取得したり、エラーメッセージを整形したりするのに便利です。

“`lua
— エラーハンドラ関数 (スタックトレースを付けてエラーメッセージを返す例)
local function error_handler(err)
print(“— Error Handler Called —“)
print(“Original error:”, err)
print(“Stack trace:”)
print(debug.traceback()) — debug.traceback() でスタックトレースを取得
print(“————————–“)
return “Handled Error: ” .. err — xpcall の第2戻り値になる
end

local function cause_error_again()
local a = nil
local b = a + 1 — nil と数値の演算でエラー
end

local success, result = xpcall(cause_error_again, error_handler)

if success then
print(“Function succeeded.”)
else
print(“Function failed. Result from handler:”, result)
end
— 出力例:
— — Error Handler Called —
— Original error: attempt to perform arithmetic on a nil value (local ‘a’)
— Stack trace:
— … (スタックトレースの詳細) …


— Function failed. Result from handler: Handled Error: attempt to perform arithmetic on a nil value (local ‘a’)
``debug.traceback()関数はdebugライブラリの一部です。debug` ライブラリは強力ですが、プログラムのパフォーマンスに影響を与える可能性もあるため、デバッグやエラーハンドリングのために使うのが一般的です。

13. ファイルI/O

標準ライブラリ io を使用して、ファイルに対する読み書きを行うことができます。

13.1 ファイルのオープンとクローズ

ファイル操作の基本は、ファイルを開き (io.open)、操作を行い、ファイルを閉じる (file:close) ことです。

io.open(filename, mode) 関数はファイルオブジェクトを返します。mode は文字列で、以下のいずれかを指定します。

  • "r": 読み込みモード (read)。ファイルが存在しない場合は nil + エラーメッセージを返します。
  • "w": 書き込みモード (write)。ファイルが存在する場合は内容を消去し、存在しない場合は新しく作成します。
  • "a": 追記モード (append)。ファイルが存在する場合は末尾に追記し、存在しない場合は新しく作成します。
  • "r+": 読み書きモード。既存のファイルを開きます。
  • "w+": 読み書きモード。ファイルを消去または作成します。
  • "a+": 読み書き追記モード。末尾に追記するか、新しいファイルを作成します。

モードに "b" を追加するとバイナリモードになります (例: "rb", "wb+")。

“`lua
— ファイルを開く
local file, err = io.open(“example.txt”, “w”)

if file then
— 書き込みなどの操作…
file:write(“Hello, File!\n”)
file:close() — ファイルを閉じる
print(“File written successfully.”)
else
print(“Error opening file:”, err)
end

— 読み込みモードでファイルを開く
local file_read, err_read = io.open(“example.txt”, “r”)

if file_read then
— 読み込み操作…
local content = file_read:read(“*a”) — ファイル全体を読み込む
print(“File content:\n”, content)
file_read:close()
else
print(“Error opening file for reading:”, err_read)
end
“`

ファイル操作が終わったら、必ず file:close() を呼び出すか、プログラム終了時に自動的に閉じられるようにすることが重要です。io.lines のように自動的に閉じてくれる便利な関数もあります。

13.2 読み込み (file:read)

ファイルオブジェクトの :read() メソッドは、ファイルから内容を読み込みます。引数で読み込み方法を指定します。

  • "*a": ファイル全体を読み込みます。
  • "*l": 次の行を読み込みます(改行文字は含まれません)。ファイルの終端に達すると nil を返します。
  • "*n": ファイルから数値を読み込みます。
  • number: 指定されたバイト数を読み込みます。

lua
local file = io.open("example.txt", "r")
if file then
-- 1行ずつ読み込む
print("Reading line by line:")
local line = file:read("*l")
while line do
print("> " .. line)
line = file:read("*l")
end
file:close()
end

io.lines(filename, ...) 関数は、ファイルオブジェクトを返さずに、指定されたファイルを1行ずつ読み込むためのイテレータ関数を返します。これは汎用 for ループと組み合わせて使うのに便利です。

lua
print("Reading using io.lines:")
for line in io.lines("example.txt") do
print(">> " .. line)
end -- io.lines はループ終了時にファイルを自動的に閉じます

13.3 書き込み (file:write)

ファイルオブジェクトの :write() メソッドは、ファイルに内容を書き込みます。引数には書き込みたい文字列を指定します。

lua
local file = io.open("output.txt", "w")
if file then
file:write("Line 1\n")
file:write("Line 2\n")
file:close()
print("output.txt created.")
end

13.4 標準入出力

io ライブラリは、標準入力 (io.stdin), 標準出力 (io.stdout), 標準エラー出力 (io.stderr) を表す組み込みのファイルオブジェクトも提供しています。

  • io.read(...): 標準入力から読み込みます。
  • io.write(...): 標準出力に書き込みます。
  • io.input(file): デフォルトの入力ファイルを変更します。引数なしで呼び出すと現在のデフォルト入力ファイルを返します。
  • io.output(file): デフォルトの出力ファイルを変更します。引数なしで呼び出すと現在のデフォルト出力ファイルを返します。
  • io.stdin, io.stdout, io.stderr: それぞれ標準入力、標準出力、標準エラー出力のファイルオブジェクト。

print(...) 関数は、内部的に io.stdout:write(...) を呼び出しているようなものです。

“`lua
io.write(“Enter your name: “) — プロンプト表示 (改行なし)
local name = io.read(“*l”) — 標準入力から1行読み込む

io.write(“Hello, “, name, “!\n”) — 複数の値を書き込み (自動で文字列変換)
“`

14. メタテーブルとメタメソッド

メタテーブルは、Luaのテーブルの挙動を変更するための強力な仕組みです。あるテーブル(以下「元テーブル」と呼びます)にメタテーブルを設定すると、特定の操作(要素へのアクセス、算術演算など)が行われた際に、元テーブルではなくメタテーブルに定義された「メタメソッド」と呼ばれる関数が実行されるようになります。

14.1 メタテーブルとは

メタテーブル自体も単なるテーブルです。特別なのは、それが他のテーブルに関連付けられ、そのテーブルの操作を「フック」できる点です。

setmetatable(table, metatable) 関数で、テーブルにメタテーブルを設定します。
getmetatable(table) 関数で、テーブルに設定されているメタテーブルを取得します(設定されていない場合は nil)。

“`lua
local my_table = {}
local mt = {} — これがメタテーブル

setmetatable(my_table, mt)

local retrieved_mt = getmetatable(my_table)
print(retrieved_mt == mt) — 出力: true
“`

14.2 主要なメタメソッド

メタテーブルに特定のキー名(先頭に二つのアンダースコア __ が付く)で関数を定義することで、そのテーブルに対する操作の挙動をカスタマイズできます。これらがメタメソッドです。

主なメタメソッド:

  • __index: テーブル内の存在しないキーにアクセスしようとしたときに呼び出されます。
  • __newindex: テーブル内の存在しないキーに値を代入しようとしたときに呼び出されます。
  • __add, __sub, __mul, __div, __mod, __pow, __unm (単項マイナス): 対応する算術演算子 (+, -, *, /, %, ^, 単項 -) がテーブルに適用されたときに呼び出されます。
  • __eq, __lt, __le: 対応する関係演算子 (==, <, <=) がテーブルに適用されたときに呼び出されます。
  • __call: テーブル自身が関数として呼び出されたときに呼び出されます。
  • __tostring: tostring() 関数がテーブルに適用されたときに呼び出されます。

__index の例 (継承のようなもの)

__index は、存在しないキーへのアクセスを別のテーブル(または関数)にリダイレクトするために使われます。オブジェクト指向の継承を模倣するのによく使われます。

“`lua
— デフォルト値を持つテーブル (親テーブルのようなもの)
local defaults = {
type = “Unknown”,
speed = 10,
color = “White”
}

local my_object = {
color = “Blue” — このキーは my_object 自身に存在する
}

local mt = {}
— __index メタメソッドを定義。存在しないキーへのアクセスを defaults テーブルにリダイレクト
mt.__index = defaults

setmetatable(my_object, mt)

print(my_object.color) — 出力: Blue (my_object 自身に存在する)
print(my_object.type) — 出力: Unknown (my_object に存在しないため、__index 経由で defaults から取得)
print(my_object.speed) — 出力: 10 (__index 経由で defaults から取得)
print(my_object.non_existent) — 出力: nil (defaults にも存在しないため)
``__indexに関数を指定することも可能です。その関数はmytable[key]のように呼び出された際に(mytable, key)` を引数として受け取り、戻り値がそのアクセス結果となります。

__add の例 (オペレータのオーバーロード)

__add メタメソッドを定義すると、+ 演算子をテーブルに適用できるようになります。

“`lua
local Vec2 = {} — 2Dベクトルを表すメタテーブル兼クラス定義のようなもの
local Vec2_mt = {} — メタテーブル

function Vec2.new(x, y)
local obj = { x = x, y = y }
setmetatable(obj, Vec2_mt) — インスタンスにメタテーブルを設定
return obj
end

— + 演算子をオーバーロード
Vec2_mt.__add = function(vec1, vec2)
— 2つのベクトルを加算し、新しいベクトルインスタンスを返す
return Vec2.new(vec1.x + vec2.x, vec1.y + vec2.y)
end

— tostring() をオーバーロード (print 関数などで使われる)
Vec2_mt.__tostring = function(vec)
return string.format(“Vec2(%d, %d)”, vec.x, vec.y)
end

local v1 = Vec2.new(1, 2)
local v2 = Vec2.new(3, 4)

local v3 = v1 + v2 — __add メタメソッドが呼び出される

print(v3) — 出力: Vec2(4, 6) (__tostring メタメソッドが呼び出される)
“`
このように、メタテーブルとメタメソッドを使うことで、Luaの組み込み型の挙動をカスタマイズしたり、独自のオブジェクト指向的な構造を実装したりすることができます。ただし、強力な機能である反面、使いすぎるとコードが読みにくくなることもあるため、適切な場面で使用することが重要です。

15. コルーチン

コルーチンは、協調的な(cooperative)マルチタスクを実現するための仕組みです。一般的なスレッド(プリエンプティブなマルチタスク)とは異なり、コルーチンは自分自身で明示的に実行を中断 (yield) し、別のコルーチンに制御を譲る必要があります。そして、中断した箇所から後で実行を再開 (resume) できます。

これは、ゲームのAIやアニメーション、非同期処理のような、複数のタスクが協調して動作する必要がある場合に便利です。

15.1 コルーチンの基本操作

coroutine 標準ライブラリを使用します。

  • coroutine.create(function): コルーチンを作成します。引数としてコルーチンの本体となる関数を指定します。コルーチンオブジェクトを返します。作成時点では実行は開始されません。
  • coroutine.resume(co, arg1, ...): コルーチンの実行を開始または再開します。最初の引数はコルーチンオブジェクト、それ以降の引数はコルーチン本体関数または yield からの戻り値として渡されます。実行成功 (true) とその結果、または実行失敗 (false) とエラーメッセージを返します。
  • coroutine.yield(...): 現在実行中のコルーチンを中断し、制御を resume を呼び出した側に返します。引数は resume の戻り値として渡されます。
  • coroutine.status(co): コルーチンの現在の状態を返します(”running”, “suspended”, “normal”, “dead”)。
  • coroutine.wrap(function): コルーチンを作成し、そのコルーチンを呼び出すための関数を返します。この関数を呼び出すたびに、コルーチンが再開されます。エラーや yield はそのまま呼び出し元に伝播します。

“`lua
local function my_coroutine_func()
print(“Coroutine started”)
local arg1 = coroutine.yield(“yield value 1”, “another value”) — yield で中断し、値を返す
print(“Coroutine resumed with:”, arg1)
local arg2 = coroutine.yield(“yield value 2”)
print(“Coroutine resumed again with:”, arg2)
return “Coroutine finished” — return で終了し、値を返す
end

— コルーチンの作成
local co = coroutine.create(my_coroutine_func)
print(“Coroutine status after create:”, coroutine.status(co)) — 出力: suspended

— 最初の実行または再開
local success, val1, val2 = coroutine.resume(co)
print(“Resume 1 result:”, success, val1, val2)
print(“Coroutine status after resume 1:”, coroutine.status(co)) — 出力: suspended

— 2回目の再開
local success, val3 = coroutine.resume(co, “resume arg 1”) — yield の戻り値として “resume arg 1” が渡される
print(“Resume 2 result:”, success, val3)
print(“Coroutine status after resume 2:”, coroutine.status(co)) — 出力: suspended

— 3回目の再開 (コルーチンは return で終了する)
local success, val4 = coroutine.resume(co, “resume arg 2”)
print(“Resume 3 result:”, success, val4)
print(“Coroutine status after resume 3:”, coroutine.status(co)) — 出力: dead
“`

実行結果:
Coroutine started
Resume 1 result: true yield value 1 another value
Coroutine status after resume 1: suspended
Coroutine resumed with: resume arg 1
Resume 2 result: true yield value 2
Coroutine status after resume 2: suspended
Coroutine resumed again with: resume arg 2
Resume 3 result: true Coroutine finished
Coroutine status after resume 3: dead

コルーチンは非常に強力な機能であり、特に非同期処理やイテレータの実装において有効です。

16. LuaとC言語の連携 (概要)

Luaの大きな強みの一つは、C/C++コードとの連携が容易なことです。これはLua-C APIという標準的なインターフェースを通じて行われます。

  • CからLuaを呼び出す: C/C++アプリケーションからLuaスクリプトを実行したり、Lua関数を呼び出したりできます。これにより、アプリケーションにスクリプトによる拡張性を持たせることができます。
  • LuaからCを呼び出す: C/C++で記述された関数やライブラリをLuaから呼び出すことができます。パフォーマンスが重要な処理や、Luaで直接行えないシステムレベルの操作をC/C++で実装し、それをLuaから利用するのに使われます。

Lua-C APIは「スタック」という仮想的なデータ構造を使って、CとLuaの間で値の受け渡しを行います。スタックに値をプッシュしたりポップしたりすることで、引数の受け渡しや戻り値の取得を行います。

このAPIの詳細な解説は入門の範囲を超えますが、Luaがどのように他の言語と連携しているかを知っておくことは、応用例を理解する上で役立ちます。多くのLuaライブラリ(例: データベース接続、ネットワーク通信など)はこのAPIを使ってCで実装されています。

17. 組み込みライブラリ

Luaには、基本的な機能を提供するためのいくつかの標準ライブラリが組み込まれています。これらは require を使わずにデフォルトで利用できるもの(print, type など、base ライブラリの一部)と、require でロードする必要があるもの(io, math, string, table, os, package, debug, coroutine)があります。

  • base: Luaの基本的な機能(グローバル変数、型変換、エラー処理など)を提供します。print, type, tostring, tonumber, pcall, error, assert などが含まれます。
  • package: モジュールと require 関数に関連する機能を提供します。package.path などがあります。
  • string: 文字列操作のための関数を提供します。string.len, string.sub, string.find, string.gsub, string.format など、強力なパターンマッチング機能も含まれます。
  • table: テーブル操作のための関数を提供します。table.insert, table.remove, table.sort, table.concat などがあります。
  • math: 数学関数と定数を提供します。math.sin, math.cos, math.sqrt, math.random, math.pi, math.huge などがあります。
  • io: ファイルI/Oや標準入出力のための機能を提供します。io.open, io.read, io.write, io.lines などがあります。
  • os: オペレーティングシステム関連の機能を提供します。日時 (os.time, os.date), 環境変数 (os.getenv), プログラム実行 (os.execute), ファイル削除/リネーム (os.remove, os.rename) などがあります。
  • debug: デバッグ目的の関数を提供します。スタックトレース (debug.traceback) や変数情報の取得など、高度な機能が含まれますが、注意して使用する必要があります。
  • coroutine: コルーチンを操作するための関数を提供します。

これらのライブラリを使いこなすことで、より幅広いタスクをLuaで効率的に行うことができます。各ライブラリの詳細は、Luaの公式リファレンスマニュアルを参照してください。

18. まとめ

この記事では、Lua言語の入門から基本的な操作までを網羅的に解説しました。

  • Luaが軽量で組み込みに適したスクリプト言語であること。
  • 開発環境の構築方法と、インタプリタの実行方法。
  • 基本的な文法(コメント、予約語、大文字小文字、ステートメント)。
  • Luaの8つのデータ型(nil, boolean, number, string, function, userdata, thread, table)。
  • 変数(ローカルとグローバル)と複数代入。
  • 様々な演算子(算術、関係、論理、文字列連結、長さ)。
  • 制御構造(if, while, repeat, for, break, return, goto)。
  • 関数(定義、呼び出し、戻り値、可変長引数、ローカル関数、クロージャ)。
  • Luaの中心であるテーブル(作成、アクセス、配列/辞書としての利用、組み込み関数)。
  • コードの分割と再利用を可能にするモジュールと require
  • 実行時エラーを捕捉する pcallxpcall
  • ファイルや標準入出力を扱う io ライブラリ。
  • テーブルの挙動をカスタマイズするメタテーブルとメタメソッド。
  • 協調的なマルチタスクを実現するコルーチン。
  • C言語連携の概要。
  • 便利な標準ライブラリ。

Luaの学習は、これらの基礎をしっかりと理解することから始まります。特にテーブルと関数はLuaの根幹をなす要素であり、その柔軟性を理解することがLuaマスターへの鍵となります。

学習の次のステップ:

  1. 実際にコードを書く: ここで学んだことを活かして、様々な小さなプログラムを書いてみましょう。インタプリタの対話モードやスクリプト実行で試行錯誤することが重要です。
  2. 標準ライブラリを深く知る: string, table, math などのライブラリには、便利な関数がまだまだたくさんあります。公式リファレンスマニュアルなどを参照して、どんな機能があるか調べてみましょう。
  3. 応用分野の学習: 興味のある分野(ゲーム開発、組み込みなど)でLuaがどのように使われているかを調べ、関連するフレームワークやライブラリに触れてみましょう。
  4. Lua-C API: より深くLuaを知りたい場合や、C/C++アプリケーションとの連携が必要な場合は、Lua-C APIについて学習するのも良いでしょう。
  5. コミュニティに参加: Luaユーザーのコミュニティ(フォーラム、メーリングリスト、Discordなど)に参加して、質問したり他の人のコードを見たりすることも学習に役立ちます。

Luaはシンプルでありながら非常にパワフルな言語です。この記事が、あなたのLuaプログラミングの旅の良いスタートとなることを願っています。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール