函式表 |
|
__call
元方法的表之間的區別很小。我稱後者為「函式表」,它們非常有用。例如,編寫一個可以快取特定函數結果的「函式表」幾乎是很簡單的事。透過使用 __call
元方法,我們可以讓該表用作一個函數;事實上,我們甚至可以賦予它原本函數的名稱,讓變更幾乎看不見。
__call
元方法(請參閱 LuaVirtualization)。其中之一正是 __index
元方法本身,結果是函式表無法用作 __index
元方法;它們必須包覆在一個真正的函數中。但函式表可用於功能組合、柯里化等。
我第一個 Memoize 的實作是寫給 Lua 4;這裡提供經更新的 Lua 5 版本
do -- The marker table serves double duty. It is a private key in the -- hash, and also serves as the hashes metatable. local marker = {} -- If the key is not found, we use the saved function (stored with the -- private key) to generate a value, and save it. function marker.__index(t, k) local val = t[marker](k) t[k] = val return val end -- We make the table look like a function, just deferring to lookup function marker.__call(t, k) return t[k] end -- They'll hit an endless loop if they do Memoize(nil). So we do -- something reasonable. We could also report an error, of course. function Memoize(fn) local self = {[marker] = fn or function(x) return nil end} setmetatable(self, marker) return self end end
很不幸地,這會在函式表中儲存一個私人金鑰,影響表的迭代。ThomasWrensch 提出了一個有用的建議,即所有快取記憶體化的函數都可以儲存在一個弱表中,這是一個微小的變更。不過,我偏好使用以下這個解決方案,它使用閉包,儘管它會為每個要快取记忆體化的函數建立兩個表和一個閉包。
function Memoize(fn) fn = fn or function(x) return nil end return setmetatable({}, { __index = function(t, k) local val = fn(k) t[k] = val return val end, __call = function(t, k) return t[k] end }) end
Dejay Clayton 提供了下列增強功能,支援物件方法、具有多個引數和傳回值的方法、具有 nil 引數和傳回值的方法、弱參考以利快取記憶體值,以及一個「__forget」方法,用於清除特定記憶體化實例的所有記憶體化結果
function pack( ... ) return arg end function memoize( fn ) local function fnKey( ... ) local key = "" for i = 1, table.getn( arg ) do key = key .. "[" .. tostring( arg[ i ] ) .. "]" end return key end local object = { __call = function( targetTable, ... ) local key = fnKey( ... ) local values = targetTable.__memoized[ key ] if ( values == nil ) then values = pack( fn( ... ) ) targetTable.__memoized[ key ] = values end if ( table.getn( values ) > 0 ) then return unpack( values ) end return nil end, __forget = function( self ) self.__memoized = {} end, __memoized = {}, __mode = "v", } return setmetatable( object, object ) end
函式表的另一個非常有用的範例,是一個具有非演算法例外項式的函數。例如,在 plural()
的實作中,我們可以將例外項式快取在表中,保留函數供演算法案例使用:例如適當地加上「s」或「es」。這就像快取記憶體化的函式表,只不過它不記憶住結果。(它們不不相容;快取記憶體器可以用非演算法例外項式「加基底」,這常常很有用。)
可以說,這一切都只是語法的把戲,但它是觀察程式結構的一個有趣方式,我感謝 Lua 賦予我這種觀點。例如在 plural()
的案例中,傳統寫法是用一些類型的例外項式清單撰寫複數函數,但這會分散對複數化演算法的注意力,而且還需要某種 API 來新增至例外項式清單,而函式表可以無縫地新增至例外項式清單
plural = To_Functable({}, function(word) local gsub = string.gsub local nsubs -- some sample rules: -- if word ends in consonant "y", change "y" to "ies" word, nsubs = gsub(word, "([bcdfghjklmnpqrstvwxyz])y$", "%1ies") if nsubs > 0 then return word end -- if word ends in a sibilant, append "es" word, nsubs = gsub(word, "([sxz])$", "%1es") if nsubs > 0 then return word end word, nsubs = gsub(word, "([cs]h)$", "%1es") if nsubs > 0 then return word end -- otherwise append "s" return word .. "s" end) -- standard exceptions (many omitted) plural.mouse = "mice" plural.man = "men" plural["right-of-way"] = "rights of way" -- maybe we like some old-fashioned usages plural.cow = "kine"
作函式表的較長範例,這裡有一個很巧妙的啤酒瓶數實作(儘管它遠不如 Philippe 的精簡)
function To_Functable(t, fn) return setmetatable(t, { __index = function(t, k) return fn(k) end, __call = function(t, k) return t[k] end }) end -- Functable bottles of beer implementation spell_out = { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", [0] = "No more", [-1] = "Lots more" } spell_out = To_Functable(spell_out, function(i) return i end) bottles = To_Functable({"Just one bottle of beer"}, function(i) return spell_out(i) .. " bottles of beer" end) function line1(i) return bottles(i) .. " on the wall, " .. bottles(i) .. "\n" end line2 = To_Functable({[0] = "Go to the store, Buy some more,\n"}, function(i) return "Take one down and pass it around,\n" end) function line3(i) return bottles(i) .. " on the wall.\n" end function song(n) for i = n, 0, -1 do io.write(line1(i), line2(i), line3(i - 1), "\n") end end
-- RiciLake
function memoize(fn) local t = {} return function(x) local y = t[x] if y == nil then y = fn(x); t[x] = y end return y end end
可以在下列找到更多有趣的元方法:
其他 memoize 實作