LoginSignup
79
85

More than 5 years have passed since last update.

Lua 5.1 言語仕様メモ

Last updated at Posted at 2014-05-06

Lua 5.1 リファレンスマニュアル の第2章と Programming in Lua プログラミング言語Lua公式解説書 の気になった部分のメモです。
間違いや誤解があるかもしれないので、正確な情報はリンク先か原文を参照することをお勧めいたします。

書籍の方は通販で手に入れるのが難しいようなので、リアル書店で見つけた場合は入手しておくとよいかもしません。

-- メモ

値と型

  • Lua は動的型付け

    • 変数は型を持たない
    • 値が型を持つ
    • 型定義の構文はない
  • 全ての値がファーストクラス

    • いかなる値も変数に格納できる
    • 他の関数に引数として与えられる
    • 全ての値は関数の戻り値にできる

基本型8種

  1. nil
    • nil値の型
      • というか、この型の値は nil しかない
    • 役に立つ値がない、ということを表す
    • テーブル要素の削除や、グローバル変数の削除にも使われる
      • ただの Null値 という感じではない
  2. ブーリアン
    • true / false
  3. 数値
    • 実数 (double)
      • 整数表現は 10^14 までは誤差は発生しない
    • 内部表現を float, long などに変えたい場合は Luaインタプリタをリビルドする
  4. 文字列
    • 文字の配列
    • 8bitクリーン
  5. 関数
    • 関数
  6. ユーザーデータ
    • 任意のC言語のデータをLuaの変数に格納する
    • CとのAPIを通してのみ作成と変更が可能
    • 代入と等価比較演算以外、Lua側で演算が定義されていない
    • メタテーブル という機能を用いると、演算を定義できる
  7. スレッド
    • 実行されているスレッドを表す
    • コルーチン実装のために使われる
    • LuaのスレッドとOSのスレッドは別物
  8. テーブル
    • 連想配列
    • nil 以外の任意の値をキーにできる
    • nil 以外のあらゆる型を値に持てる
    • Lua唯一のデータ構造
      • 通常の配列のほか以下を表現するためにも使われる
        1. 記号表
        2. 集合
        3. レコード
        4. グラフ
        5. ツリー
    • a["name"] でフィールドにアクセス
      • a.name でもアクセス可能
    • テーブルには関数を格納できる
      • 関数がファーストクラスなので
      • つまり、テーブルにメソッドを持たせることができる
      • JSっぽい

偽の値

  • 条件判断で 偽 となるのは
    1. nil
    2. false

つまり、空文字列や空のテーブル、 0 などは になるので注意

オブジェクト

以下の型は オブジェクト である

  1. テーブル
    • Luaのテーブルは動的に確保された オブジェクト
  2. 関数
  3. スレッド
  4. ユーザーデータ
  • オブジェクトは変数の格納されず、参照されるのみである
    • 代入、引数渡し、関数の返り値は、コピーではなく参照が渡される
テーブルついて
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の変数は三種類

  1. グローバル変数
    • 環境テーブル または 環境 と呼ばれる Lua テーブル内のフィールドとして存在する
      • グローバルな領域からアクセスできるテーブルのフィールドが、グローバル変数
      • nil を代入するとグローバル変数を削除できる
  2. ローカル変数
    • グローバル変数より高速にアクセスできる
    • スコープを抜けるとガベージコレクタによって解放される
      • メモリ節約
    • レキシカルスコープ
      • 定義されたスコープ内から自由にアクセス可能
      • つまり、中の関数は外側の関数のローカル変数にアクセスできる
  3. テーブルフィールド

変数のルール

  • 明示的にローカル宣言されない変数は全てグローバル変数
  • 関数の仮引数はローカル変数
  • 代入が行われる前の変数の値は、自動的に 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 である

ブロックは以下のいずれか

  1. 制御構造のボディ
  2. 関数のボディ
  3. チャンク
    • 変数が宣言されているファイル or 文字列

do end で囲む

  • do を入力すると、end が来るまでコマンドがしない

  • 明示的なブロック宣言

  • 変数のスコープをコントロールするのに便利

    • ローカル変数のスコープは、宣言されたブロックとなる
  • returnbreak をブロックの途中に仕込むのにも使える

    • 構文上、 breakreturn はブロックの最後にしか置けないから
    • 言い換えると、 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 ini の部分を 制御変数

    • 変数が複数ある場合、先頭の変数が制御変数
    • 制御変数が 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種

  1. and
    • 最初の引数が
      • 偽ならその値を返す
      • 真なら二番目の引数を返す
  2. or
    • 最初の引数が
      • 真ならその値を返す
      • 偽なら二番目の引数を返す
  3. 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

論理演算子のイディオム

  1. 変数のみ定義時のデフォルト値を設定する
-- 未定義変数だった場合, x = 'hoge' を実行
if not x then x = 'hoge' end
-- これは以下と等価
x = x or 'hoge'
  1. 関数のデフォルト引数とする
function increment(n)
    n = n or 1  -- n が未定義時は 1
    return n+1
end
  1. 三項演算子の再現
-- 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 関数で変更できる

環境

以下のオブジェクトは、 メタテーブル の他に、 環境 という関連付けられたテーブルを持つ

  1. スレッド
    • グローバル環境と呼ばれる
    • スレッドと、スレッドが作成した(ネストされていない)関数のデフォルトの 環境 として使われる
      • Cのコードに直接アクセスできる
  2. 関数
    • その関数がアクセスするグローバル変数の解決に使われる
    • 関数が内部で生成する関数のデフォルト環境としても使われる
      • 生成元の環境を引き継ぐ
  3. ユーザーデータ

ガベージコレクション

コルーチン

協調スレッド

コルーチンは4つの状態の持つ
状態は coroutine.status 関数で確認できる

  1. suspended
    • 中断
    • コルーチン生成時のデフォルトの状態はこれ
  2. running
    • 実行中
  3. dead
    • 終了
    • いちど dead になると他に状態には移行できない
  4. normal
    • 通常
    • suspended ではないが、別のコルーチンが実行中である
  • yield 関数を呼んで、明示的に実行を中断しなければならない

コルーチンのフロー

作成

coroutine.create を呼ぶ

  • 引数に関数を与える
  • 新しいコルーチンを作成し、 スレッドオブジェクト を返す

実行

  • コルーチンはすぐには実行されない
    • coroutine.resume 関数を呼び出すと実行される
    • 関数の終わりか、 yield を呼ぶまで実行される

中断

  • yield を呼ぶと中断
    • 対応する coroutine.resume から戻る
    • resume は true を返す
    • yield に引数がある場合は、それも返す
    • 同じコルーチンを resume すると、中断し箇所から再開
    • 再開時に引数を渡すと、 復帰先の yield からそれらが返される

終了

2つの場合がある

  1. メイン関数から return して正常終了したとき
    • true とメイン関数の戻り値を返す
  2. エラーで異常終了した時
    • 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関数

loadfileloadstring はファイルや文字列をチャンクとして読み込み、コンパイルする。
このとき、コンパイルはグローバル環境で行われる
コンパイルされたチャンクは関数として返される

f = loadstring("i = i + 1")
print(f)  -- function: 0xhogehoge
f()  -- グローバル変数 i が無いためエラー
i = 0
f()  -- OK
print(i)  -- 1

エラー処理

エラーを通知する方法は大きく2つ

  1. エラーコードを返す
    • 大体は nil を返す
    • 容易に回避できないエラーはこっちを使うことが多い
  2. 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 というライブラリをロードする
79
85
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
79
85