Zaawansowane funkcjonalności LUA |
Fpekal « Citoyen » 1566222120000
| 10 | ||
Może ktoś mnie kojarzy, albo nie, ale kiedyś byłem jedną z tych osób, które starały się pisać skrypty z Zamówień na kody. Równolegle z tym uczyłem się C++ i zauważyłem pewien problem. W LUA nie ma bezpośrednio klas i obiektów. Sprawiało to, że wiele rzeczy było bardzo nieeleganckie i z czasem pisania powstawał jeden wielki kod spaghetti. Strasznie to utrudniało późniejszą edycję kodu. Głównie dlatego opuściłem Transformice i przeszedłem już na serio na C++, oraz na inne języki. Ostatnio zaciekawiło mnie to, w jaki sposób działa tworzenie trybów w Roblox'ie (chyba nie powinienem się do tego przyznawać xd). Bardzo się zdziwiłem, gdy zobaczyłem, że wszystko jest tam napisane w LUA i jest zrobione obiektowo. Zacząłem sprawdzać jak to jest zrobione i natrafiłem na określenie metatable, oraz na wykonywanie funkcji za pomocą dwukropka. W tym wątku zamierzam omówić mniej lub bardziej dokładnie z czym to się je i tak dalej, bo uważam, że jest to dość ciekawe i może pomóc innym w tworzeniu bardziej złożonych skryptów/trybów. Głównym warunkiem tego, czy wam się to wszystko przyda jest to, czy rozumiecie jak działają funkcje i klasy/obiekty, bo nie mam zamiaru tu tego tłumaczyć. Jeżeli ktoś zauważy jakiś kujący w oczy błąd gramatyczny, albo programowy, albo będzie chciał coś jeszcze dopowiedzieć: droga wolna; i tak za niedługo znowu stąd pewnie pójdę xd Głównym założeniem programowania obiektowego w LUA jest, że tak naprawdę nie programujemy obiektowo. Może to za pierwszym razem zabrzmieć dziwnie, ale wszystko co robimy, to jest tworzenie funkcji, które będą tworzyć tablice i je odpowiednio modyfikować. Dla przykładu zróbmy klasę obsługującą liczby zespolone (nie jest konieczne rozumieć, czym one są, ale wydają mi się najlepszym przykładem) Na sam początek należy zrobić konstruktor, który będzie zwracał tablicę z polami (w przyszłości też metodami). Radzę trzymać wszystkie konstruktory danej klasy w jednej tablicy, nie blokują wtedy niepotrzebnie nazw konstruktorów dla innych klas. Code Lua 1 2 3 4 5 6 7 8 9 local Complex = {} Jak na razie nie różni się to niczym od zwykłej tablicy z dwoma indeksami; i właśnie dobrze, bo tak miało być. Teraz możemy sprawdzić, czy wszystko działa: Code Lua 1 2 3 local liczbaZlozona = Complex.new(4,5) -- Tworzy liczbę zespoloną: 4+5i Jak widać, wszystko jak na razie działa poprawnie. No ale co nam po zwykłych polach? W sumie to nic. Więc dlatego teraz zajmijmy się metodami. Będziemy je tworzyć normalnie jak funkcje w tablicy: Code Lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 local Complex = {} i test: Code Lua 1 2 3 local zesp = Complex.new(3,4).multiply(Complex.new(2,5)) -- (3+4i) * (2+5i) W ten sposób stworzyliśmy fajną podstawę do właściwych podpunktów tego wątku. Do czego służą metatable? Do przeładowywania operatorów. Znaczy to tyle, że możemy używać znaków + - * / itp., oraz wbudowanych funkcji typu tostring() na naszych obiektach. Metatable jest tablicą funkcji o specjalnych nazwach, do których potem odwołuje się nasz skrypt, gdy natrafi na odpowiedni znak. Oto przykład: Code Lua 1 2 3 4 5 6 7 8 9 10 11 12 local mt = { Ale skąd program ma wiedzieć do której tablicy ma dołączyć dany metatable? Do tego służy funkcja setmetatable(table,metatable). W drugą stronę działa getmetatable(table). Kod testujący: Code Lua 1 2 3 4 local test = {a=2} Z tą wiedzą możemy poprawić nasz poprzedni kod w ten sposób: Code Lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 local Complex = {} Dzięki temu możemy użyć takiej składni: Code Lua 1 2 3 local zesp = Complex.new(3,4) * Complex.new(2,5) Listę wszystkich dostępnych możliwych przeładowań można znaleźć tutaj. Jak widać jest tam nawet możliwe wywoływanie tablicy jak funkcję, czyli można podrasować dodatkowo naszą tablicę Complex, żeby nie trzeba było używać funkcji new(), ale to już zostawiam wam ;) Nie uważałbym tą funkcjonalność za jakąś kluczową i bardzo ważną, ale może czasami pomóc w pisaniu mniejszego kodu. Chodzi o to, że jeżeli stworzymy jakąś metodę, która jako pierwszy argument przyjmuje obiekt, do którego należy, to nie musimy tego argumentu podawać. Chyba najlepiej jest to pokazać na przykładzie: Code Lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Class = {} Jakiś czas po napisaniu tego wątku natrafiłem na pewną dokumentację LUA, w której jest opisana funkcja class(). Działa ona tak, że tworzy klasy, które mają możliwość DZIEDZICZENIA. Próbowałem na potrzeby tego wątku zrobić taki mechanizm, ale miałem lekkie problemy, bo źle podchodziłem do tematu. Tutaj to działa tak, że podczas samego tworzenia klasy cała zawartość klasy, z której się dziedziczy, najpierw jest kopiowana do nowej klasy, a potem dopiero dokładane są nowe wartości. Kod funkcji class() Code Lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 function class(base, init) Po analizie kodu można zobaczyć, że klasy są tworzone w troszeczkę inny sposób, niż ja to wcześniej pokazywałem. Mianowicie CAŁA klasa jest zapisana w metatable, głównie w metamethod'zie (np. __mul, __index itp.) __index. Są 4 możliwe tryby tworzenia klasy: czysty, z dziedziczeniem, z konstruktorem, z dziedziczeniem i konstruktorem: - Czysty - local Klasa = class() - BrudnyZ dziedziczeniem - local Klasa = class(klasaBazowa) - Z konstruktorem - local Klasa = class(funkcja) - Oba - local Klasa = class(klasaBazowa, funkcja) Tworzenie obiektów klasy: local obiekt = Klasa(argumentyKonstruktora) Teraz zupełnie poważny przykładowy program wykorzystujący tę funkcję: Code Lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 function class(base, init) Jak widać w linijce 58, należy samemu odpalać konstruktor klasy bazowej, ponieważ stary konstruktor jest nadpisywany nowym, a potem dopiero jest on wykonywany. Dernière modification le 1566257880000 |
Boxofkrain « Censeur » 1566299400000
| 1 | ||
Dobra robota! Ja się przyznam, że nie ogarniam programowania obiektowego w Lua, jest strasznie dziwne w porównaniu do innych języków. |
Fpekal « Citoyen » 1566313380000
| 3 | ||
Ta "dziwność" głównie wynika z tego, że kod nie jest kompilowany, tylko jest przepuszczany przez interpretator o wiele lepiej brzmi to po angielsku. Dzięki temu np. język pozwala na wykonywanie stringów, tak jakby były kodem Gdyby nie to, że deweloperzy wyłączyli tę opcję, to można by było robić super rzeczy. Teoretycznie można by napisać własny interpretator... Może kiedyś spróbuję i go gdzieś tu udostępnię. Jakby tak teraz o tym pomyśleć, to byłoby to potężne. W sumie to można by było spróbować zrobić go tak, że najpierw w zmiennej znajdowała by się referencja do tablicy _G, a potem w pętli odczytywać kolejne poziomy tej tablicy np. _G -> tfm -> exec i dostawać się na każdy kolejny poziom dzięki zapisowi tablica[nazwaTekstowaKolejnegoPoziomu], aż w końcu dostało by się do jakiejś funkcji typu giveCheese() i sprawdzić jakie argumenty miały by być do niej przesłane i jeżeli byłaby to kolejna funkcja to po prostu rekurencyjnie odpalić interpretator i czekać na to co zwróci, a jeżeli była by tam liczba albo jakaś operacja arytmetyczna, to ją wyliczyć i dać jako argument. TEORETYCZNIE się da, ale dużo z tym zachodu. Końcowo chciałbym napisać, żeby nie myśleć o zmiennych w LUA jako o zwykłym kwadratowym pojemniku, do którego można wkładać tylko kwadraty, ale trójkąty już nie pasują. Zmienna w LUA to bardziej polimorf (?), który dostosowuje swój kształt do tego, co ma w środku. Jak ktoś wie, czym jest klasa polimorficzna, to w tym momencie powinna mu zabłysnąć czerwona lampka ;) |
Youseksiak « Censeur » 1566326220000
| 1 | ||
nic nie rozumiem ale fajne |
Grejapl « Censeur » 1566461220000
| 0 | ||
Dobra robota |
Szczurb « Censeur » 1566659160000
| 0 | ||
Przydatny wątek! Dobrze gdyby przypięli ;) |