Pytanie Lua - Jak przekazać funkcję obiektu jako parametr innej funkcji


local a = {}
function a:test1(value)
    print(value)
end
local b = {}
function b:test2(v1, v2)
    v2(100);
end
b:test2(_, a.test1)

Nie działa. Wartość jest zerowa. Mógłbym znaleźć rozwiązanie z hermetyzacją w anonimowej funkcji

b:test2(variable, function(value) a:test1(value) end)

Ale uważam, że to całkiem kiepskie

Jaka jest prawidłowa składnia?


11
2017-11-16 18:54


pochodzenie


Dlaczego nie przekazujesz samego obiektu? - hjpotter92
mniej elastyczny. oznaczałoby to, że b musi wykonać wywołanie funkcji test1, ograniczając w ten sposób jej użycie do obiektu - Rayjax


Odpowiedzi:


anotherObject:aFunction(variable, object.doStuff) jest poprawna składnia.

Używanie dwukropka : z funkcją to po prostu cukier syntaktyczny dla połączenia lub deklaracji z niejawnym self parametr jako pierwszy argument. Jeśli chcesz zastosować wzór pokazany w twoim przykładzie w czystszy sposób, możesz użyć funkcji pomocnika.

local function bind(t, k)
    return function(...) return t[k](t, ...) end
end

Następnie stosujesz to w taki sposób.

anotherObject:aFunction(variable, bind(object, 'doStuff'))

Edytować: Uważam, że rozwiązanie twojego problemu będzie wymagało wiązania na pewnym poziomie, bez uciekania się do modyfikacji interpretera Lua lub użycia kroku tłumaczenia kodu. Dzieje się tak głównie dlatego, że funkcje w Lua nie zawierają żadnych informacji na ich temat pochodzenie. To znaczy., tabele nie są z natury posiadać funkcje, które oni sklep.

Na przykład: poniżej jest całkowicie zgodny z prawem kod Lua.

function Circle:area() -- function Circle.area(self)
    -- ...
end

-- Evaluate the function in the "area" slot with Square as the self parameter.
Circle.area(Square)

Oczywiście, możesz spróbować zmienić paradygmat, ale może być na to za późno, jeśli budujesz całą aplikację opartą na idei funkcji powiązanych z tabelą, z której zostały one zindeksowane, jak powiedziałeś. Dlatego proponuję następujące zwariowany rozwiązanie.

local mt = {}

function mt:__index(k)
    local v = self._slots[k]

    if v == nil then
        -- Ascend the inheritance tree.

        -- This has to be done with rawget all the way up,
        -- otherwise inherited functions would be repeatedly bound.
        local p = self

        repeat
            p = rawget(p, '_parent')
            if not p then break end
            v = p._slots[k]
        until v
    end

    if type(v) == 'function' then
        -- Return a self-bound version of the function.
        return function(...) return v(self, ...) end
    end

    return v
end

function mt:__newindex(k, v)
    self._slots[k] = v
end

--- Demo & Tests ---

local function Object(parent)
    local o = setmetatable({_slots = {}}, mt)
    if parent then rawset(o, '_parent', parent) end
    return o
end

local o1 = Object()
local o2 = Object(o1)

assert(o1.abc == nil, 'o1.abc should be nil')
o1.abc = 3
assert(o1.abc == 3, 'o1.abc should be 3')
assert(o2.abc == 3, 'o2.abc should be 3, inherited from o1')
o2.abc = 7
assert(o2.abc == 7, 'o2.abc should be 7, overriding o1')
assert(o1.abc == 3, 'o1.abc should be 3, unaffected by o2 setter')

function o1:test(bar)
    return self.abc + bar
end

assert(type(o1.test) == 'function', 'o1.test should be a function')
assert(type(o2.test) == 'function', 'o2.test should be a function, inherited from o1')

assert(o1.test(5) == 8, 'o1.test(5) should return 3 + 5 = 8')
assert(o2.test(11) == 18, 'o2.test(11) should return 7 + 11 = 18')

function o2:test2(fn)
    return self.abc + fn(7)
end

assert(o2.test2(o1.test) == 17, 'o2.test2(o1.test) should return 7 + (3 + 7) = 17')

o2.test3 = o1._slots.test -- proper function copying
assert(o2.test3(11) == 18, 'o2.test3(5) should return 7 + 11 = 18')

o2.abc = nil
assert(o2.abc == 3, 'o2.abc should be 3 again, inherited from o1 after clearing')

o2.abc = false
assert(o2.abc == false, 'o2.abc should be false, __index needs to differentiate between nil and false')

Ta metatabela zapewni ci to, czego potrzebujesz, z dziedziczonymi i związanymi funkcjami do rozruchu. Musisz tylko upewnić się, że wszystkie tabele, które chcesz zastosować do tego wzoru, również są zgodne z metodą tworzenia obiektu pokazaną w przykładowym kodzie.

Aby wyjaśnić, każdy stół wykonany w ten sposób ma jakiekolwiek nowe zadanie przekierowane do _slots pod-tabela i wszelkie nowe pobieranie sprawdzone _parent drzewo dziedziczenia. Jeśli typem jest wartość function, następnie zwraca nowe zamknięcie z oryginałem self które rozpoczęło kontrolę związaną z znalezioną funkcją.

Oczywiście, wywołanie funkcji z jednego z tych obiektów z : składnia dwukropka będzie głupim pomysłem, ponieważ oceni to o.fn(o, o), a to prawdopodobnie nie jest to, czego chcesz. Innym zastrzeżeniem jest to, że funkcje kopiowania na te obiekty, od te obiekty nie będą działać zgodnie z oczekiwaniami. o1.newfn = o2.fn położy o2 związana funkcja w o1, które z kolei zostaną ponownie powiązane o1. Końcowym rezultatem będzie coś takiego o2.fn(o2, o1). Będziesz musiał skopiować funkcje z _slots stół.


Podsumowując: Mimo że to działa, nie polecałbym go osobiście na dłuższą metę, ponieważ może to być mylące dla osób używających Lua do pracy z tabelami, indeksowaniem i funkcjami, a także będzie być nad głową. Możesz być w stanie usunąć niektóre z nich za pośrednictwem pamiętanie zamknięcia, ale decyzję pozostawiam tobie. Powodzenia!


10
2017-11-16 19:38



Mimo, że twoja odpowiedź jest świetna, a twoja droga jest prawdopodobnie czystsza, budowałem całą aplikację za pomocą zorientowanego obiektowo wzorca, który opiera się na manualu lua: lua.org/pil/16.html (patrz na dole). Używanie funkcji wiązania pomocnika oznaczałoby przepisanie całej zawartości - Rayjax
@Rayjax Zaktualizowałem swoją odpowiedź za pomocą pełnego rozwiązania. Proszę daj mi znać co myślisz. Jeśli chodzi o obiektowy wzór przedstawiony w Programowanie w Lua, Nie wierzę, że jest tam coś, co odnosi się do lub zachęca do korzystania z funkcji w sposób, w jaki chcesz z nich korzystać. Mam nadzieję, że wyjaśniłem to z moją odpowiedzią. - Ryan Stein
Jak powiedziałeś, twoje podejście jest szalone, choć bardzo interesujące. Myślę, że jak zbudowałem około 80% mojego obecnego projektu, przejdę dalej i skończę z wzorcem, którego użyłem. Wiem, że istnieje kilka sposobów implementacji programowania OO w lua, według ciebie, jaki jest najlepszy? - Rayjax
@Rayjax Podobnie jak w przypadku wielu rzeczy, to zależy. Zależy to od tego, co robisz, czego potrzebujesz i czego nie potrzebujesz. Zwykle zaczynam od najprostszych struktur i pracuję stamtąd. W Lua można oczywiście zastosować różne style OO. Więc nie sądzę, że istnieje jakiś absolutnie najlepszy styl. Chciałbym jednak powiedzieć, że myślę, że używanie prostych tabel z dziedziczeniem opartym na prototypach jest najprostszym stylem OO. Oto jest przykład o co mi chodzi. Idź z prądem i staraj się nie walczyć z językiem i tym, co zapewnia. - Ryan Stein


twój kod będzie działał. powód, dla którego powiedział Ryan. Wątpię, aby w funkcji anotherObject: aFunction () używano niewłaściwego sposobu wywoływania object.stuff. Prawidłowy sposób:

local a = {}
function a:test1()
    print(1)
end
local b = {}
function b:test2(v1, v2)
    v2();
end
b:test2(_, a.test1)

-1
2017-11-18 05:28



Twoja funkcja działa, ponieważ: test1 nie otrzymuje żadnego parametru. Spójrz na parametr - wypisuje zero lokalna funkcja a = {} a: test1 (wartość) print (wartość) koniec lokalna b = {} funkcja b: test2 (v1, v2) v2 (100); koniec b: test2 (_, a.test1) - Rayjax
(Edytuj odpowiedź, aby usunąć zaznaczenie) - Rayjax