Luaリファレンス 要注意点 ~演算子オーバロード、メタメソッド~

演算子オーバーロード

pythonと同じ方針。メタメソッドに他の一般メソッドの参照を代入する

Set = {}
local _mtSet = {}               --> メタテーブル用

function Set.new (l)
    local set = {}
    setmetatable(set, _mtSet)   --> テーブルに対してメタテーブルが用意されるようにする。
    for _, v in ipairs(l) do set[v] = true end
     return set
end

function Set.union(a, b)
    local res = Set.new{}
    for k in pairs(a) do res[k] = true end
    for k in pairs(b) do res[k] = true end
    return res
end

s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1, 60}
print(getmetatable(s1))  --> table : 00000000xxxxx1  
print(getmetatable(s2))  --> table : 00000000xxxxx2

_mtSet.__add = Set.union                   --> メタテーブル __add に代入する(ことで、「+」の振る舞いを変える

s3 = s1 + s2    --->s3 = Set.union(s1, s2) --> {1,10,20,30,50, 60}

演算子種類

__add+ 演算。
__sub- 演算。__add演算と同じように動作する。
__mul* 演算。__add演算と同じように動作する。
__div/ 演算。__add演算と同じように動作する。
__mod% 演算。__add演算と同じように動作する。
__pow^ (累乗) 演算。__add演算と同じように動作する。プリミティブ演算として関数 pow (Cの数学ライブラリ) を使う。
__unm単項 - 演算。
__concat.. (連結) 演算。
__len# 演算。
__eq== 演算。a ~= b は not (a == b) と等価である。 (よって __neqは無い)
__lt< 演算。a > b は b < a と等価である.  (よって __geは無い)
__le<= 演算。a >= b は b <= a と等価である。(よって __gtは無い) __leメタメソッドがなければ、 Luaは a <= b を not (b < a) とみなして__ltを試みることに注意。


5.2までは & や| は無い。
5.2まではビット演算やシフト演算の類は、元々LUAには存在しないので注意。

演算子種類 Lua 5.3

5.3では、ビット演算子や整数除算の演算子が増えてる。

__band& (ビットごとの論理積) 演算。__add 演算と同様の動作をしますが、メタメソッドが試みられるのはいずれかの引数が整数でなく、整数に変換可能な値でもない場合です。
__bor| (ビットごとの論理和) 演算。__band 演算と同様の動作をします。
__bxor~ (ビットごとの排他的論理和) 演算。__band 演算と同様の動作をします。
__bnot~ (ビットごとの単項否定) 演算。__band 演算と同様の動作をします。
__shl<< (ビットごとの左シフト) 演算。__band 演算と同様の動作をします。
__shr>> (ビットごとの右シフト) 演算。__band 演算と同様の動作をします。
__idiv// (切り捨て除算) 演算。__add 演算と同様の動作をします。

文字列化用のメタメソッド

__tostring文字列化する用途。print関数はtostring関数を内部的に呼び出す。

tostringは__tostringメタメソッドが定義されていれば、tostring関数にわたったオブジェクトを引数として__tostringメタメソッドを呼び出す。
これを前提(呼び出し関係と、引数関係)として__tostringを組む。

メタメソッドの適応順序

a 演算子 b という表記を見つけた場合、
LUAはまずa が「演算子」のメタメソッドを持っているかを検索する。
もっていれば、 そのメタメソッドに割り当てられた関数を実行する。

もっていなければ、bが「演算子」のメタメソッドを持っているかを検索する。
それもなければ、そのメタメソッドに割り当てられた関数を実行する。
それすらなければエラーとなるであろう。

比較メソッド

比較メソッド(関係演算子メソッド)は型が異なると適応できない。
算術演算子は型がことなってもどちらかが算術メソッドを持っていれば適応可能。

メタメソッドの追加等をロック

対象のオブジェクトに自分で追加等した後(別に追加しなくても良いが)、
対象のオブジェクトにメタメソッドの入れ替え(_mtSetの入れ替え)をロックしたい場合、
メタテーブルの__metatableメタメソッドに真を突っ込む。(通常は文字列を突っ込む)。
(※_metSetへの差し替え、getmetatableによる読み込みを抑えるだけなので、
  一度採用してしまった_mtSetがさらにメタテーブルを持つのは抑えられないもよう。
  ある意味中途半端な仕様のようだ。)

Set = {}
local _mtSet = {}               --> メタテーブル用

function Set.new (l)
    local set = {}
    setmetatable(set, _mtSet)   
    for _, v in ipairs(l) do set[v] = true end
        return set
end

function Set.union(a, b)
    local res = Set.new{}
    for k in pairs(a) do res[k] = true end
    for k in pairs(b) do res[k] = true end
    return res
end

s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1, 60}
print(getmetatable(s1))  --> table : 00000000xxxxx1  
print(getmetatable(s2))  --> table : 00000000xxxxx2

_mtSet.__add = Set.union              
_mtSet.__metatable = "not your business"  --メタテーブルをロック。真の値を入れさえすればよい。

print(getmetatable(s1))     --> not your business
_mtSet._mul = Set.union
setmetatable(s1, {})          --> cannot change protected metatable

__index

テーブルの特定の値取得時に、対応するキーが無い場合に呼び出される。

Window = {}
--デフォルト値を持つプロトタイプを作成
Window.prototype = { x = 0, y = 0, width = 100, height = 100 }
Window.mt = {} -- メタテーブルの作成。  --通常はこのようにメタテーブルを与えたいオブジェクト自信のプロパティとして含めた方が良いだろう。

--コンストラクタ
function Window.new (o)
   setmetatable(o, Window.mt)
   return o
end

--indexメタメソッドの定義

Window.mt.__index = function(table, key)
   return Window.prototype[key]
end


w = Window.new{x=10, y=30}
print(w.height)  --> 存在しないので、オブジェクトに帰属するメタテーブルの__indexが呼ばれる。結果Window.prototype["height"]が呼ばれる。

rawget

__indexを定義したが、__indexを経由せず呼び出したい場合、rawget(t, i)を利用する

w = Window.new{x=10, y=30}
print(rawget(w, "width"))    ---> nil
print(rawget(w, "x"))          ---> 10

__newindex

テーブルの特定の値設定時に、対応するキーが無い場合に呼び出される。
こちらは基本的に代入を主目的として呼び出されることとなる。

この場合、__newindexを経由しない代入には、rawset(t, k, v)が対応する。

デフォルト値を持つテーブル

function setDefault(t, d)
    local mt = {__index = function() return d end }
    setmetatable(t, mt)
end

tab = {x=10, y=20}
print(tab.x, tab.z) -->10, nil

setDefault(tab, 0)
print(tab.x, tab.z) -->10, 0

テーブルを横断して、1つのメタテーブルで済ます。しかし安全に一意な値を使う。

local key = {}   -- これはkeyとしてアドレスを使用することで一意性を確保する
local mt = {__index = function (t) return t[key] end }
function setDefault(t, d)
    t[key] = d
    setmetatable(t, mt)
end

-- keyは「table: 000000005FD180」 みたいな値となる(実行する度に変わる)

読み取り専用テーブル

function ReadOnly(t)
    local proxy = {}    --> 中間のプロキシテーブル
    local mt = {        --> プロキシテーブルは要素が全部存在しない。これで全てがトレースされる。
        __index = t,   --> 読み取りはトレースする必要がない。追跡(リダイレクト)する必要がない場合には、テーブル自身を指定できる。 
        __newindex = function(t, k, v)
           error("attempt to update a read-only table", 2)
        end
    }
    setmetatable(proxy, mt)
    return proxy
end

days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}

メタメソッド __call

__call: 値が関数として評価されて呼ばれたときに呼ばれます。

function function_event (func, ...)
    if type(func) == "function" then
        return func(...)   -- プリミティブの call
    else
        local h = metatable(func).__call
        if h then
            return h(func, ...)
        else
            error(···)
        end
    end
end

トップ   差分 履歴 リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-01-29 (金) 00:00:00