Luaの基本コンセプト

https://www.lua.org/manual/5.4/manual.html

  • nil
    • 「他のどの値とも異なる」ということを表す、型でもあり値でもある
    • 条件式に与えらえるとfalseと同義に評価される
  • boolean
    • true, false
  • number
    • 32bit整数、あるいは実数(32bit単精度浮動小数)。
    • オーバーフロー注意
  • string
    • イミュータブルなバイト列、8bit-clean
    • エンコーディングに依存しない
  • function
    • C言語あるいはLuaで書かれた関数
  • userdata
    • C言語のデータで表される任意の型をLuaで扱えるようにしたもの
    • Lua中で作ったり変更することはできず、C API経由でのみ扱うことができる
  • thread
    • 実行中のスレッドとは独立したスレッドの概念でコルーチンの実装に使われる
    • OSのスレッドとは全く別のもので関係はない
  • table
    • 配列、連想配列の方が近く、インデックスには整数以外を設定できる
      • ただし、nilとNaN(Not a Number、特別な浮動小数の値)はインデックスにはできない
    • インデクサは、arr["name"]の形の他、シンタックスシュガーとして arr.name の形も使用可

table, function, thread, userdataはオブジェクトであり、コピーした時には参照のみコピーされ、値はコピーされない。

_ENV テーブルと _G テーブル

変数 var を宣言したとき、 _ENV.var と翻訳される。_ENV テーブルは環境(Environment)と呼ばれる。グローバル変数 var を宣言したとき、_G.var となる形で保存される。_G テーブルはグローバル環境(Global Environment)と呼ばれる。

var = 3.14
print(var)         --=> 3.14
print(_ENV.var)    --=> 3.14
print(_ENV._G.var) --=> 3.14
print(_G.var)      --=> 3.14
print(_G._ENV.var) --=> ERROR

_ENV がすべての変数を保存するテーブルで、_G はその一部なので _G._ENV は参照できない。

エラーハンドリング

error() での明示的なエラー発生、pcall() あるいは xpcall() によるエラー発生時の補足が行える。
xpcall() はエラー発生時にコールバック関数を呼べる。

function raise_error(some_val)
    error("CUSTOM ERROR")
    return some_val
end

function error_handler(error_obj)
    something = 1
    print(error_obj)
    return something
end


some_val = 1
p_ret, func_ret = pcall(raise_error, some_val)
print(p_ret)    --=> エラーが起きたのでfalse
print(func_ret) --=> エラーオブジェクト
xp_ret, func_or_hdlr_ret = xpcall(raise_error, error_handler, some_val)
print(xp_ret)           --=> エラーが起きたのでfalse
print(func_of_hdlr_ret) --=> error_handlerの戻り値

メタテーブルとメタ関数

すべての変数は、メタテーブルと呼ばれるアンダースコア2つから始まる名前のテーブルを持つ。例えば__addは、+演算子で操作する場合に実際に行われる処理の内容を表す。このメタテーブルの値を変更することで所望の動作にすることができる。
getmetatableでこのメタテーブルの値を取得し、setmetatableで値を設定できる。

(詳細は今度)

ガベージコレクション

Lua上のオブジェクトは参照されなくなったら自動でメモリが解放される。(ただしファイナライザーが呼ばれた場合は復活する可能性があるのと、デバッグライブラリを用いた操作の場合は除く)
ガベージコレクションには2種類、incrementalモードとgenerationalモードがある。
デフォルトのGCモードは多くのユーザにとって十分なモードになっている。変更したい場合は collectgbarge を呼び設定すること。

コルーチン

コルーチンとよぶ協調的マルチスレッディングが使える。実行中のスレッドとは別のスレッドで処理を行い、コルーチンの途中 yield() を呼ぶことで一時中断することができる。
corouteing.create() でコルーチン(thread型のオブジェクト)を作成する。croutine.resume() を呼ぶことでコルーチンを実行する。関数がすべて実行されるか、coroutine.yeilds() が呼ばれるまで実行される。

coroutine.resume() が正常に終了した(すべて実行、あるいは coroutine.yields() が呼ばれた)場合には true と、続けて戻り値が返る。エラーが発生した場合 false とエラーオブジェクトが返る。
coroutine.yields() は、ネストされた関数であっても呼ばれた場合直ちにメインスレッドに戻る。

function foo (a)
    print("foo", a)                             -- *0
    return coroutine.yield(2*a)                 -- *A
end

co = coroutine.create(
    function (a,b)
        print("co-body", a, b)                  -- *1
        local r = foo(a+1)
        print("co-body", r)                     -- *2
        local r, s = coroutine.yield(a+b, a-b)  -- *B
        print("co-body", r, s)                  -- *3
        return b, "end"                         -- *4
    end
)

print("main", coroutine.resume(co, 1, 10))
--=> *1が実行される("co-body 1, 10")
--=> *0が実行される("foo 2")
--=> *Aのyieldでメインスレッドに戻る("main true 4")
print("main", coroutine.resume(co, "r"))
--=> *Aのyieldの戻り値がresumeの引数"r"として続く
--=> *2が実行される("co-body r")
--=> *Bのyieldでメインスレッドに戻る("main true 1+10 1-10")
print("main", coroutine.resume(co, "x", "y"))
--=> *Bのyieldの戻り値がresumeの引数"x", "y"として続く
--=> *3が実行される("co-body x y")
--=> *4が実行されメインスレッドに戻る("main true 10 end")
print("main", coroutine.resume(co, "x", "y"))
--=> すでにcoは実行完了しているので失敗する("main false error-obj")

コルーチン自身を返すのではなく coroutine.resume() を勝手にしてくれる関数を返す&エラーを直接呼出し元に返す coroutine.wrap() もある(癖が強いので使いどころは難しい)。