*Luaリファレンス 要注意点 ~オブジェクト指向~ [#kee16b7e] **インボカント ~暗黙の第1引数self~ [#ja94c102] -PerlやPythonでは、明記必須のインボカント。Luaにも存在します。~ インボカントこそ、手続き型言語をオブジェクト指向化する中核概念といえるでしょう。 ~ #sh(lua){{ Account = { balance = 0 } function Account.withdraw (v) Account,balance = Account.balance - v end }} これはテーブル内にメソッド的なものを定義した例ですが、これだと #sh(lua;highlight:[2]){{ a = Account Account = nil a.withdraw(10) ---> エラー }} となってしまいます。 -そこで、第1引数とインボカントが活躍します。 #sh(lua){{ function Account.withdraw ( self, v ) self.balance = self.balance - v end }} -多くの言語では、1番目のself(またはthisとなっている言語も多い)の引数は見えなくなっています。 #sh(lua;highlight:[1]){{ function Account:withdraw( v ) ---> 「.」ではなく、「:」を使うと、1番目の引数にselfを書いたことと同じこととみなされる。 self.balance = self.balance - v end }} -呼び出しも「:」を使えば第1引数にオブジェクト自体を代入したことと同じとなる。~ 通常記法も使えるがその場合はやや間が抜けた記述となるだろう。~ #sh(lua;highlight:[4]){{ a = Account Account = nil a.withdraw(a, 10) --> 通常記法。第1引数にテーブル(オブジェクト)自身を渡す。 a:withdraw(10) --> シンタックスシュガー。第1引数にオブジェクト自身を渡していることになる。 }} **インボカントとインスタンス化 [#zbefa9c6] -インボカント #sh(lua;){{ Account = { balance = 0 } function Account:new (o) --「:」に注意 o = o or { } setmetatable(o, self) self.__index = self return o end function Account:deposit (v) self.balance = self.balance + v end a = Account:new{ balance = 0 } a:deposit(100) }} としたとすると、 #sh(lua;){{ Account:new { balance = 0 } }} などといったように使用すると、メタテーブルの__indexに対して~ #sh(lua;){{ Account.__index = Account }} となる。~ ~ そして、a:deposit(100)は a.deposit(a, 100)となるので、~ aに存在しないdepositメソッドは~ getmetatable(a).__index.deposit(a, 100) と検索される。~ ~ 結果、''Account.deposit(a, 100)'' と指定したことと同じことになる。~ ~ 即ち、aは自身にメソッドが存在しない時、Account(を一種親的にみなし)呼び出す、 一種、オブジェクト指向の「継承」や「デフォルト値」があるかのような機構を構築出来るということである。 -フィールドの初期値の継承 又、''フィールドの初期値も継承''する。~ 例えば、~ #sh(lua;){{ b = Account:new() print(b.balance) -- b.balance は b自体には存在しないので、Account.balance を見ることとなる。 }} 一方、これにたいして、 #sh(lua;){{ b:deposit(10) }} などと呼び出した場合は、Account.deposit(b, 10)が呼び出される形となり、b.blanace = b.balance + 10 されるので、~ この段階で、b自身にbalanceフィールドが形成されることとなる。~ -クラスの継承 #sh(lua;){{ Account = { balance = 0 } function Account:new (o) o = o or { } setmetatable(o, self) self.__index = self return o end function Account:deposit(v) self.balance = self.balance + v end function Account:withdraw(v) if v > self.balance then error ("んな金ねーぉ") end self.balance = self.balance - v end }} ここで、このAccountを継承するSpecialAccountクラスを作成する #sh(lua;){{ SpecialAccount = Account:new() -- この段階ではまだインスタンス s = SpecialAccount:new{limit = 1000.00} -- new を実行した時、selfのパラメータがSpecialAccountとなり、self.__indexもSpecialAccountとなった。 }} これにより、sのメタテーブルはSpecialAccountとなり、sはSpecialAccountのインスタンスであるかのように振舞える。~ ~ #sh(lua;){{ s:deposit(100) }} を呼び出すと、~ ''getmetatable(s).__index.deposit(s, 100)となるので、''~ ''ScecialAccount.deposit(s, 100)を実行しようとするが、このdepositも存在しないため、''~ ''getmetatable(SpecialAccount).__index.deposit(s, 100)となり、''~ ''結果、''~ ''Account.deposit(s, 100)が実行される。''~ ~ ''即ち、インスタンス、子クラス、親クラスの、メソッド探査(階層チェーンの伝播)が構築できたこととなるのである。'' -又、SpecialAccountは独自のメソッドを持ったり、上書き(オーバライド)するのになんら問題はない。 #sh(lua;){{ function SpecialAccount:withdraw (v) --オーバライド if v - self.blance >= self:getLimit() then error("引き出しすぎ") end self.balance = self.balance - v end function SpecialAccount:getLimit() return self.limit or 0 end }} 又、プロトタイプ言語であることからわかるように、''いちいちクラスメソッドの単位でオーバーライドせず、インスタンス単位で変えても良い''(もともと区分けがないとも言えるが…) #sh(lua;){{ function s:getLimit() return self.balance * 0.10 end }} これで、s:withdrawから呼び出されるgetLimitは、このs:getLimitが呼ばれることとなる。 **多重継承 [#g65ffd69] -Account クラスと Named クラスを多重継承する。 #sh(lua;){{ Account = { balance = 0 } function Account:new (o) o = o or { } setmetatable(o, self) self.__index = self return o end function Account:deposit(v) self.balance = self.balance + v end ---- Name = {} function Named:getname() return self.name end function Named:setname(n) self.name = n end --- local function search (k ,plist) for i=1, #plist do local v = plist[i][k] if v then return v end end end function MultiInheritance (...) -- 複数のクラスから local c = {} -- 新しいクラス local parents = {...} setmetatable(c, {__index = function(t, k) local v = search(k, parents) -- 該当プロパティを持つ親クラスを特定する t[k] = v -- パフォーマンス改善のためコピーしておく return v end}) c.__index = c -- 新しいクラスのnewコンストラクタを定義 function c:new (o) o = o or { } setmetatable(o, c) return o end return c end NamedAccount = MultiInheritance(Account, Named) account = NamedAccount:new{name="Paul"} }} **プライベート [#wc5b1824] -Perlと同じやり方。~ ~ ただ、わかりにくくなるので、LUAではあまりやらない。~ アンダーバーでも付けて、人と運用のレベルでカバーするのがLUAでは普通で~ 強制的に文法で対応しようとはしない方が良い。 #sh(lua;){{ function newAccount ( initialBalance) local self = {balance = initialbalance} local deposit = function (v ) self.balance = self.balance -v end local getBalance = function () return self.balance end return { deposit = deposit getBalance = getBalance } end acc = newAccount(100) print(acc.getBalance()) }} **オブジェクトの簡易実装はクロージャで [#raeea973] >まぁ、他の言語でも全く同じ使用用途。 しかし、複雑化すると、結局クラス化することが多い #sh(lua;){{ function newObject(value) return function(action, v) if action == "get" then return value elseif action == "set" then value = v else error "invalid action" end end end d = newObject(0) print(d("get")) --> 0 d("set", 10) print(d("get")) --> 10 }}