Lua 5.1 リファレンスマニュアル の第2章と Programming in Lua プログラミング言語Lua公式解説書 の気になった部分のメモです。
間違いや誤解があるかもしれないので、正確な情報はリンク先か原文を参照することをお勧めいたします。
書籍の方は通販で手に入れるのが難しいようなので、リアル書店で見つけた場合は入手しておくとよいかもしません。
-- メモ
値と型
-
Lua は動的型付け
- 変数は型を持たない
- 値が型を持つ
- 型定義の構文はない
-
全ての値がファーストクラス
- いかなる値も変数に格納できる
- 他の関数に引数として与えられる
- 全ての値は関数の戻り値にできる
基本型8種
- nil
-
nil値の型
- というか、この型の値は nil しかない
- 役に立つ値がない、ということを表す
- テーブル要素の削除や、グローバル変数の削除にも使われる
- ただの Null値 という感じではない
- ブーリアン
- true / false
- 数値
- 実数 (double)
- 整数表現は 10^14 までは誤差は発生しない
- 内部表現を float, long などに変えたい場合は Luaインタプリタをリビルドする
- 文字列
- 文字の配列
- 8bitクリーン
- 関数
- 関数
- ユーザーデータ
- 任意のC言語のデータをLuaの変数に格納する
- CとのAPIを通してのみ作成と変更が可能
- 代入と等価比較演算以外、Lua側で演算が定義されていない
- メタテーブル という機能を用いると、演算を定義できる
- スレッド
- 実行されているスレッドを表す
- コルーチン実装のために使われる
- LuaのスレッドとOSのスレッドは別物
- テーブル
- 連想配列
- nil 以外の任意の値をキーにできる
- nil 以外のあらゆる型を値に持てる
- Lua唯一のデータ構造
- 通常の配列のほか以下を表現するためにも使われる
- 記号表
- 集合
- レコード
- グラフ
- ツリー
- 通常の配列のほか以下を表現するためにも使われる
-
a["name"]
でフィールドにアクセス-
a.name
でもアクセス可能
-
- テーブルには関数を格納できる
- 関数がファーストクラスなので
- つまり、テーブルにメソッドを持たせることができる
- JSっぽい
偽の値
- 条件判断で 偽 となるのは
- nil
- false
つまり、空文字列や空のテーブル、 0 などは 真 になるので注意
オブジェクト
以下の型は オブジェクト である
- テーブル
- Luaのテーブルは動的に確保された オブジェクト
- 関数
- スレッド
- ユーザーデータ
- オブジェクトは変数の格納されず、参照されるのみである
- 代入、引数渡し、関数の返り値は、コピーではなく参照が渡される
テーブルついて
a = {} -- テーブルを作成し、参照を取得
a[1] = 'hoge'
print(a[1]) -- 'hoge'
b = a -- 同じ参照先を取得
a = nil -- 参照を削除
print(b[1]) -- 'hoge'
c = {[1] = 1, [2] = 2; ['a'] = 'a'} -- 区切り文字には , ; の二種が使える
type
関数で型名の文字列が取得できる
変換
Lua は文字列と数値を実行時に自動的に変換
- 演算の状況に応じて文字列⇔数値の変換をLua側がよしなに行ってくれる
- ちゃんと制御したいときは
string.format
のドキュメントを読むこと
変数
Luaの変数は三種類
- グローバル変数
-
環境テーブル または 環境 と呼ばれる Lua テーブル内のフィールドとして存在する
- グローバルな領域からアクセスできるテーブルのフィールドが、グローバル変数
- nil を代入するとグローバル変数を削除できる
- ローカル変数
- グローバル変数より高速にアクセスできる
- スコープを抜けるとガベージコレクタによって解放される
- メモリ節約
- レキシカルスコープ
- 定義されたスコープ内から自由にアクセス可能
- つまり、中の関数は外側の関数のローカル変数にアクセスできる
- 定義されたスコープ内から自由にアクセス可能
- テーブルフィールド
変数のルール
- 明示的にローカル宣言されない変数は全てグローバル変数
- 関数の仮引数はローカル変数
- 代入が行われる前の変数の値は、自動的に nil
メタテーブル によってグローバル変数とテーブルフィールドへアクセスする効果を変更できる
-
gettable_event
という関数の呼び出しと等価- この関数は Lua から定義したり呼び出すことはできない
- 説明のために用いているだけ
インデックス付き変数へのアクセス
hoge = t[i]
-- これは以下と等価 (実際には呼び出せない)
hoge = gettable_event(t, i)
グローバル変数へのアクセス
- 各関数は独自に 環境 への参照を持つ
- その関数内のグローバル変数は、 環境テーブル を参照する
- 関数が作られるとき、作られた関数から見える 環境 を受け継ぐ
- Lua関数の 環境テーブル を
- 取得するには
getfenv
を呼ぶ - 変更するには
setfenv
を呼ぶ
- 取得するには
- その関数内のグローバル変数は、 環境テーブル を参照する
-- グローバル変数 x へのアクセスは以下と等価
-- _env は、その関数が実行されている 環境 を表す
gettable_event(_env, "x")
文
CやPascalのように文のセットをサポートしている。以下は文の一種。
- 変数宣言
- 代入
- テーブルコンストラクタ
- 関数呼び出し
- 制御構造
チャンク
Lua の実行の単位を チャンク と呼ぶ
- 順番に実行される連続した文
- 文末のセミコロンは省略可
- 空文は許可されない
-
;;
はエラー
-
- 空文は許可されない
-- チャンク
a = 'hoge'; b = 'fuga'; function f(x, y) return x..y end; print(f(a, b))
-- 以下のチャンクはエラー
d = 1; e = 1; print(d+e);;print('hoge')
-- これはOK
a = 1 b = 2
チャンクの実体
- 可変引数を持つ無名関数
- 関数なのでローカル変数を宣言できる
- 引数を受け取れる
- 戻り値を返せる
ブロック
構文的にはチャンクと同じ
-
block ::= chunk
である
ブロックは以下のいずれか
- 制御構造のボディ
- 関数のボディ
- チャンク
- 変数が宣言されているファイル or 文字列
do end
で囲む
-
do
を入力すると、end
が来るまでコマンドがしない -
明示的なブロック宣言
-
変数のスコープをコントロールするのに便利
- ローカル変数のスコープは、宣言されたブロックとなる
-
return
やbreak
をブロックの途中に仕込むのにも使える- 構文上、
break
とreturn
はブロックの最後にしか置けないから - 言い換えると、
end
else
until
などの直前にしか書けない
- 構文上、
function f()
return -- エラー
print('hoge')
end
function f2()
do return end -- OK
print('hoge')
end
代入
多重代入ができる
-- python ライクに以下のコードで値のスワップができる
x, y = y, x
制御構文
for
for i in array
のような for文を ジェネリックfor と呼ぶ
-
for i in
の i の部分を 制御変数- 変数が複数ある場合、先頭の変数が制御変数
- 制御変数が nil になるとループ終了
-
内部で イテレータ を使う
- イテレータは、実質的ただのクロージャ
-- for i in {} と同じ動作
function make_iter(t)
local i = 0
return function()
i = i + 1
return t[i]
end
iter = make_iter{1, 2, 3}
while true do
local val = iter() -- イテレータが尽きるまで while が回る
if val == nil then
break
end
print(val)
end
ローカル宣言
ローカル変数は、ブロック内のどこでも宣言できる
イディオム
グローバル変数の値を保護しながらローカル変数化する
do
local hoge = hoge
end
式
関数呼び出しを ()
で包むと、戻り値が1つになる。
{}
で包むとテーブルにして返す。
function f(x, y, z)
return x, y, z
end
print( f(1, 2, 3) ) -- 1, 2, 3 が出力
print( (f(1, 2, 3)) ) -- 1 のみ出力
print( {f(1, 2, 3)} ) -- {[1] = 1; [2] = 2; [3] = 3} が返る
print( f(1, 2, 3), 4 ) -- 1, 4
print( 4, f(1, 2, 3) ) -- 4, 1, 2, 3
-- 式の途中で呼ばれたときは、最初の引数のみ得られる
print( f(1, 2, 3) .. 4 ) -- '14'
print( 4 .. f(1, 2, 3) ) -- '41'
関係演算子
常に true / false のどちらかを返す
t = {1, 2, 3}
print("0" == 0) -- false
print(t[1] == t["1"]) -- false
論理演算子
以下の3種
- and
- 最初の引数が
- 偽ならその値を返す
- 真なら二番目の引数を返す
- or
- 最初の引数が
- 真ならその値を返す
- 偽なら二番目の引数を返す
- not
- true / false のいずれかを返す
なお論理演算子は、必要になるまで2つ目の引数を評価しない。
-- and
print(0 and 1) -- 1
print(true and 2) -- 2
print(3 and true) -- true
print(nil and 1) -- nil
print(false and 1) -- false
-- or
print(0 or 1) -- 0
print(nil or 1) -- 1
print(nil or false) -- false
print(false or nil) -- nil
-- not
print(not 1) -- false
print(not {}) -- false
print(not nil) -- true
print(not false) -- true
print(not not true) -- true
-- 引数の評価
function f(x)
return x+1
end
print(0 and f(1)) -- 2
print(nil and f(2)) -- nil
論理演算子のイディオム
- 変数のみ定義時のデフォルト値を設定する
-- 未定義変数だった場合, x = 'hoge' を実行
if not x then x = 'hoge' end
-- これは以下と等価
x = x or 'hoge'
- 関数のデフォルト引数とする
function increment(n)
n = n or 1 -- n が未定義時は 1
return n+1
end
- 三項演算子の再現
-- Python の hoge = a if cond else b は以下と等価
hoge = (cond and b) or c
-- 値の大きいを方を取得する例
hoge = ((a > b) and a) or b
長さ演算子
#
を使う
- 文字列はバイト数を返す
- テーブルは、テーブルの長さ
t = {[1] = 1; [2] = 2}
print(#'a') -- 1 (byte)
print(#'あ') -- 3 (byte)
print(#{}) -- 0
print(#t) -- 2 (テーブルの長さ)
t['a'] = 'b'
print(#t) -- 2
function f(x)
return x
end
t2 = {[f(1)] = 11;'x'; y = 'y'; [2] = 2}
print(#t2) -- 2
長さ演算を使うイディオム
a = {[1] = 1, [2] = 2, [3] = 3}
print(a[#a]) -- 末尾要素の出力
a[#a] = nil -- 末尾要素の削除
a[#a+1] = hoge -- 末尾に値を追加
テーブルコンストラクタ
テーブルを作る式
- 評価されるたび、新しいテーブルが作られる
- 最も単純なコンストラクタ式は
{}
- コンストラクタはテーブルの初期化時のみ影響を与える
- 作成されるテーブルの性質は同じ
- 最も単純なコンストラクタ式は
関数
関数呼び出し
メソッド呼び出し
-
v:name(...)
はv.name(v, ...)
と等価
関数定義
Lua の関数は全て無名関数。
- python でいう lambda
- よくある
filter
,map
関数に引数として渡す関数ように扱うこともできる
- よくある
-- 無名関数を print
print( function (x) return x end )
-- 無名関数を return
function gen_func(x)
return function (a)
return a + x
end
end
関数名とは、関数を保持する変数のこと。
-- 以下の2つは等価
function f(x) return x end
f = function (x) return x end
名前付き引数
テーブルを引数に与えると、名前付き引数と同等の機能を得られる
function _f(a, b)
print(a + b)
end
function f(args)
-- 必須引数のチェック
if type(args.a) ~= "number" then
error("a は数値")
end
_f(args.a,
args.b or 0 -- b はデフォルト値を与えておく
)
-- 呼び出し
print( f{a=1, b=1} ) -- 2
print( f{a=1} ) -- 1
print( f{b=1} ) -- error
末尾呼び出し
例
function g()
return 1
end
function f()
return g() -- ここが末尾呼び出し
end
関数f() は g() を呼び出した時点でやることがないので、スタックから排除される。
これは、 g() の実行が終了するとき、 f() のスタックを経由せずに f() を呼び出した場所までコントロールが戻る。
いわゆる 末尾再帰最適化 に通じるもの。
よって、以下は末尾呼び出しではない。
function f(x)
g(x) -- 返り値を return していない => 戻り値を廃棄する処理が残っている
end
function f2(x)
return g2(x) + 1 -- 加算演算が残っているので f2 のスタックを捨てれない
end
末尾再帰の例
-- 階乗
function fact(n, acc)
local acc = acc or 1
if (n < 1) then return acc end
return fact(n - 1, n * acc)
end
エラー処理
error
関数を呼ぶと、明示的なエラーを発生させることができる
Lua内でエラーをキャッチしたいなら pcall
関数を使う
メタテーブル
Luaの値は メタテーブル を持てる
- 通常のLuaテーブル
- 値に対して、特殊な演算をしたときの挙動を定義する
- メタテーブルのフィールドを設定すると、オブジェクトの動作を変えることができる
- 数値以外で加算を行おうとしたとき、メタテーブルの
__add
が定義されていれば、加算のためその関数を呼び出す
- 数値以外で加算を行おうとしたとき、メタテーブルの
いわゆる演算子のオーバーロード
自分で独自のデータ型の挙動を定義できる
キーを イベント と呼ぶ
-
add
など
値を メタメソッド と呼ぶ
- 加算を行う関数など
テーブルのメタテーブル
-
setmetatable
関数で変更できる
略
環境
以下のオブジェクトは、 メタテーブル の他に、 環境 という関連付けられたテーブルを持つ
- スレッド
- グローバル環境と呼ばれる
- スレッドと、スレッドが作成した(ネストされていない)関数のデフォルトの 環境 として使われる
- Cのコードに直接アクセスできる
- 関数
- その関数がアクセスするグローバル変数の解決に使われる
- 関数が内部で生成する関数のデフォルト環境としても使われる
- 生成元の環境を引き継ぐ
- ユーザーデータ
ガベージコレクション
略
コルーチン
協調スレッド
コルーチンは4つの状態の持つ
状態は coroutine.status
関数で確認できる
- suspended
- 中断
- コルーチン生成時のデフォルトの状態はこれ
- running
- 実行中
- dead
- 終了
- いちど dead になると他に状態には移行できない
- normal
- 通常
- suspended ではないが、別のコルーチンが実行中である
-
yield
関数を呼んで、明示的に実行を中断しなければならない
コルーチンのフロー
作成
coroutine.create
を呼ぶ
- 引数に関数を与える
- 新しいコルーチンを作成し、
スレッドオブジェクト
を返す
実行
- コルーチンはすぐには実行されない
-
coroutine.resume
関数を呼び出すと実行される- 関数の終わりか、
yield
を呼ぶまで実行される
- 関数の終わりか、
-
中断
-
yield
を呼ぶと中断- 対応する
coroutine.resume
から戻る -
resume
は true を返す-
yield
に引数がある場合は、それも返す
-
- 同じコルーチンを
resume
すると、中断し箇所から再開- 再開時に引数を渡すと、 復帰先の
yield
からそれらが返される
- 再開時に引数を渡すと、 復帰先の
- 対応する
終了
2つの場合がある
- メイン関数から return して正常終了したとき
- true とメイン関数の戻り値を返す
- エラーで異常終了した時
- false とエラーメッセージを返す
coroutine.wrap
コルーチンを作成するもう一つの関数
- コルーチンではなく、
resume
する関数を返す- その関数に渡される引数は、
coroutine.resume
の追加の引数として渡される - 戻り値は 最初のブーリアン(成功、失敗) を除いた関数の戻り値
- この関数で発生したエラーは捕らえられず、内部エラーは呼び出し側に伝搬する
- その関数に渡される引数は、
コルーチン使用の実例
プロデューサ/コンシューマ
- プロデューサー
- 値を生成し続ける関数や機能
- コンシューマ
- 生成した値を使用して処理する関数や機能
この2つをコルーチンで実装する。マスタースレーブの関係を作る
-- receive(コンシューマ) がコルーチン再開の起点なので
-- コンシューマ駆動と呼ばれる設計
function receive()
local status, value = coroutine.resume(producer)
-- resume して受け取った値を何らかの関数に返す
-- producer(coroutine) は再び実行を開始し yeild(x) してxを渡すのを待っている
return value
end
function send(x)
coroutine.yeild(x)
end
producer = coroutine.create(
function()
while true do
local x = io.read()
send(x) -- yeild で動作を中断し x を resume 先に渡す
end
end
end)
コンパイル、実行
ソースは実行される前に中間言語へプリコンパイルする。
load関数
loadfile
や loadstring
はファイルや文字列をチャンクとして読み込み、コンパイルする。
このとき、コンパイルはグローバル環境で行われる
コンパイルされたチャンクは関数として返される
f = loadstring("i = i + 1")
print(f) -- function: 0xhogehoge
f() -- グローバル変数 i が無いためエラー
i = 0
f() -- OK
print(i) -- 1
エラー処理
エラーを通知する方法は大きく2つ
- エラーコードを返す
- 大体は nil を返す
- 容易に回避できないエラーはこっちを使うことが多い
-
error
関数を呼ぶ- 容易に回避できそうなエラーはこっち(が多い)
assert関数
Lua では assert
は通常のコードのなかでもエラー処理のためによく使うらしい
-- イディオム
file = assert(io.open("does-not-exist.txt", "r"))
-- stdin:1: does-not-exist.txt: No such file or directory が出力される
エラー処理
Luaは、アプリケーション側がLuaに「チャンクを実行しろ」って呼び出すことで動作を開始する。
普通は、LuaではなくLuaを呼び出した側がエラー処理を行う。
Lua側でのエラー処理が必要なときは pcall(protected call)関数 を使う。
これはコードをカプセル化する。
pcall は処理結果(true/false) と、カプセル化した関数の戻り値の2つを返す。
function f()
-- なにか処理
if not cond then error() end
-- なにか処理
print(a.x) -- aがテーブルじゃなかったらエラー
-- なにか処理
end
if pcall(f) then
-- f() 実行中にエラーが発生しない
print('success!') -- なにか処理
else
-- f() 実行中にエラー発生
print('error!') -- エラー処理
end
Luaインタプリタ
Luaインタプリタは、入力された 行 を 完結したチャンク として評価する
起動オプション
$ lua -i hoge.lua # hoge.lua を実行したあとに起動
$ lua -e "print(1 + 1)" # 直接チャンクを実行する
$ lua -i -e "require 'hoge.lua'" # -e のチャンクを実行したあと起動
$ lua -l hoge # hoge というライブラリをロードする