Page 1 of 2

Classes custom

Unread postPosted: 24 Mar 2015, 20:50
by DarkFire
Bonjour! :)

J'ai récemment mis au point un code pour créer des classes en Lua, que je pense meilleur que celui de base, puisque il gére l'héritage multiple et tout ça en peu de ligne de code! ;)
J'aimerai savoir ce que vous en pensez, et j'essayerai de voir si je peux optimiser le code où ajouter des fonctionnalité plus complexe.
Code: Select all
local setmetatable = setmetatable
local unpack = unpack
local pairs = pairs
local ipairs = ipairs
local string_match = string.match

do
   -- __call
   local function __call(self,...)
      local ins = setmetatable( {}, getmetatable(self) )

      for k,v in pairs(self) do
         if k ~= "__call" then
            ins[k] = v
         end
      end

      if ins.init then ins:init(...) end
      return ins
   end

   -- __is
   function is(o,c)
      if getmetatable(o) == getmetatable(c) then
         return true
      else
         for i,v in ipairs(o.__parents) do
            if v:is(c) then
               return true
            end
         end
      end
      return false
   end
   
   -- __super
   function super(ins, superclass)
      if ins == superclass then
         return superclass
      else
         return ins.__parents[1]
      end
   end
   
   -- Class
   function Class(...)
      -- create the new class table
      local c = {__parents={...}, is=is, super=super, __call=__call}
      -- inherit all
      for i=1,#c.__parents do
         for k,v in pairs(c.__parents[i]) do
            if not c[k] then
               c[k] = v  -- copy the parent table content into the new class
            end
         end
      end

      return setmetatable(c,c)
   end
end -- do block


Et si la fonction class de base vous suffit sur la Ti-Nspire, elle n'existe pas sur PC, donc elle reste toujours utile! :#top#:

Re: Classes custom

Unread postPosted: 24 Mar 2015, 20:58
by Adriweb
C'est une bien vieille idée de vouloir améliorer le système de classe existant :P Ca a été fait (cf. Jim Bauwens), mais peu utilisé il me semble.
Enfin bref, ça peut être utile, quand on en a besoin, certes :)

Quelques trucs:
- Tu ne copies pas les metatables des parents - c'est voulu ?
- Le problème avec l'héritage multiple, c'est si plusieurs méthodes sont valables (même signatures) - laquelle appeler ?
- Je vais demander à Jim de faire un tour sur ce topic :P

Pour info, le code de class([parent]) de TI (source Wiki Inspired-Lua):
Show/Hide spoilerAfficher/Masquer le spoiler
Code: Select all
class = function(prototype)
local derived={}
   if prototype then
      function derived.__index(t,key)
         return rawget(derived,key) or prototype[key]
      end
   else
      function derived.__index(t,key)
         return rawget(derived,key)
      end
   end
   function derived.__call(proto,...)
      local instance={}
      setmetatable(instance,proto)
      local init=instance.init
      if init then
         init(instance,...)
      end
      return instance
   end
   setmetatable(derived,derived)
   return derived
end

Re: Classes custom

Unread postPosted: 24 Mar 2015, 21:30
by DarkFire
Adriweb wrote:- Tu ne copies pas les metatables des parents - c'est voulu ?

Hmm... Tu me fais réfléchir... Parce que le seul regret sur cette version, c'est que je ne peux pas tester si une classe a hérité d'une autre...
Adriweb wrote:- Le problème avec l'héritage multiple, c'est si plusieurs méthodes sont valables (même signatures) - laquelle appeler ?

C'est vrai que là, on ne peut qu'appeller la dernière méthode enregistrée... Pour gérer ça, il faudrait faire un truc avec __index et __newindex. Je vais y réfléchir. (En fait j'avais déjà bossé sur les classes en Lua pour PC il y a un moment, et je suis parvenu à un système d'héritage multiple avec l'accès et la création interdite aux attributs hors des méthodes,etc...
Mais c'était plutôt long et j'ai décidé de le réecrire en plus court sans toute ces limitations... Voila le code:
Show/Hide spoilerAfficher/Masquer le spoiler
Code: Select all
Class = {}
function Class:new(...)
   -- * Class method "new", to create new Class * --
   local mt = {__call=self.__call}
   mt.__metatable = "private stuff"
   mt.__newindex = function(t, k, v)
      if type(v) == "function" then
         t.__met[k] = v
      end
   end
   local class = setmetatable({__met={},__atr={},__a=false}, mt)
   -- inherit
   local t = unpack{{...}}
   for c in ipairs(t) do
      for i in pairs(t[c]) do
         for k, v in pairs(t[c].__met) do
            class.__met[k] = v
         end
         for k, v in pairs(t[c].__atr) do
            class.__atr[k] = v
         end
      end
   end
   -- return the class table
   return class
end

function Class:__call(...)
   -- * Class method "__call", to create instance * --
   local ins = {__met={},__atr={},__a=false}
   for k, atr in pairs(self.__atr) do
      ins.__atr[k] = atr
   end
   for k, met in pairs(self.__met) do
      ins.__met[k] = function(...)
         local old__a = ins.__a
         ins.__a = true
         local f = met(...)
         ins.__a = old__a
         return f
      end
   end
   local mt = {__metatable="private stuff"}
   mt.__index = function(t, k)
      if t.__met[k] then
         return t.__met[k]
      elseif t.__a and t.__atr[k] then
         return t.__atr[k]
      end
   end
   mt.__newindex = function(t, k, v)
      if type(v) == "function" then
         t.__met[k] = v
      elseif type(v) ~= "function" then
         t.__atr[k] = v
      end
   end

   setmetatable(ins, mt)
   ins:__init(...)
   ins.__met.__init = nil
   return ins
end


Merci pour la réponse rapide! :D

EDIT: Je pense avoir trouvé la solution pour les signatures identique, le truc serait de faire comme les fonctions memoize: on enregistre les arguments. Sauf que là on enregistre le type des arguments et non pas la valeur.

Re: Classes custom

Unread postPosted: 25 Mar 2015, 21:40
by technolapin
Je sais pas vraiment ce que c'est les classes héritées ou pas (parce que il n'y en a pas en lua nspire de base ou alors j'en ai jamais eu besoin), mais j'ai pas pu résister à canger des détails :p :
Code: Select all
do
   local function __call(ins,...)
     -- ins (que tu a appellé self) est déjà une instance parce que c'est comme ça en lua
     pcall(ins:init(...)) -- je sais pas si c'est vraiment mieux, mais j'aime bien les pcall, c'est pratique (ne retourne rien si ça fait une erreur et ignore cette erreur)
      return ins
   end

   function Class(...)
      -- create the new class table
      local c = {__call=__call}

      local parents = {...}
      -- inherit all
      for a, b in pair (parents) do -- + efficace ou au pire je suis juste un maniaque des pairs
         for k,v in pairs(b) do
            c[k] = v  -- copy the parent table content into the new class
         end
      end

      return setmetatable(c,c)
   end
end


Voilà, ça sert à rien de faire ces changements à part pour simplifier un peu et gagner 0.0001% de performance environ.

Re: Classes custom

Unread postPosted: 26 Mar 2015, 19:57
by DarkFire
A non désolé, mais la fonction __call n'est pas valide du tout: j'ai besoin de renvoyer une copie de self, pas de self lui même...
pour ce qui est des performances, un simple test booléen sera toujours plus rapide qu'une fonction et c'est pareil pour la boucle for (car pairs est une fonction). Mais sinon, c'est simpa de m'aider à optimiser mon code! ;)
( Voir https://springrts.com/wiki/Lua_Performance )

Sinon j'ai modifié mon code, maintenant il supporte:
- Les tests pour savoir si un objet est de telle ou telle classe qui est facilement accesible via une comparaison classique ou la fonction is(o, c)
- Une fonction super qui retourne soit le premier parent connu de la classe, soit l'un des parent donné en argument qui s'écrit super(ins,[superclass)
- Et très important: la suppression de la métaméthode __call lors de la création de l'objet!

Re: Classes custom

Unread postPosted: 27 Mar 2015, 12:26
by technolapin
Ah non, faire un pcall est plus rapide que de vérifier que la fonction est exécutable puis l’exécuter.
Code: Select all
local function __call(ins,...)
         pcall(ins:init(...))
         return ins
       end

revient à la même chose que
Code: Select all
local function __call(self,...)
         local ins = unpack({self})
         if ins.init then ins:init(...) end
         ins.__call = nil
         return ins
       end

mais sans le test, sans les problèmes, en plus court et plus rapide.

Re: Classes custom

Unread postPosted: 27 Mar 2015, 13:54
by DarkFire
Bon d'accord, mais pcall ou pas, là n'est pas le problème: je le répéte, mais, j'ai absolument besoin d'une copie de la table d'origine!
En programmation orientée objet, une classe est un peu comme le plan de construction d'un objet.
Avec mon code, on crée une classe comme ça:
Show/Hide spoilerAfficher/Masquer le spoiler
Code: Select all
local Car = Class()
function Car:init(max_speed)
    self.max_speed = max_speed
    self.speed = 0
    self.started = false
end

function Car:start()
    self.started = true
end

Et on l'instancie comme ça:
Show/Hide spoilerAfficher/Masquer le spoiler
Code: Select all
local voiture1 = Car(120)
local voiture2 = Car(130)
local voiture3 = car(140)

Comme tu peux le remarquer, il est possible de créer plusieurs instance de "Car", donc il faut en créer des copies.
En plus, la classe n'est censé contenir que des méthodes, et pas autre chose.
Les autres type de variables sont crée par la fonction "init".

Alors que le code que tu me propose ne copie aucune table... Donc lorsque tu appelle __call tu retourne la classe elle même.
C'est comme si une classe était un plan de construction d'une maison, et que lorsque tu envoie des ouvriers construire la maison, à la place ils modifient le plan de construction original...

Si ce n'est pas clair, je t'invite à te renseigner sur la programmation orientée objet (POO ou encore OOP pour les intimes) ;)

Re: Classes custom

Unread postPosted: 27 Mar 2015, 19:16
by technolapin
Oui mais c'est déjà une copie.
Par exemple, dans ce code, a ne varie pas:
Code: Select all
local a = "a"
local function lol (var) var = 1 end
print a
lol(a)
print a

En lua, les arguments renvoient une copie de la variable localisée dans la fonction et non pas la variable originale, la modifier ne change pas la variable originelle.

Re: Classes custom

Unread postPosted: 27 Mar 2015, 19:20
by Levak
technolapin wrote:Oui mais c'est déjà une copie.
Par exemple, dans ce code, a ne varie pas:
Code: Select all
local a = "a"
local function lol (var) var = 1 end
print a
lol(a)
print a

En lua, les arguments renvoient une copie de la variable localisée dans la fonction et non pas la variable originale, la modifier ne change pas la variable originelle.


Sauf si c'est un tableau.

Re: Classes custom

Unread postPosted: 27 Mar 2015, 19:25
by Adriweb
Levak wrote:
technolapin wrote:Oui mais c'est déjà une copie.
Par exemple, dans ce code, a ne varie pas:
Code: Select all
local a = "a"
local function lol (var) var = 1 end
print a
lol(a)
print a

En lua, les arguments renvoient une copie de la variable localisée dans la fonction et non pas la variable originale, la modifier ne change pas la variable originelle.


Sauf si c'est un tableau.


(les table en Lua étant purement des pointeurs, d'où l'intérêt de se faire des helpers du genre copyTable et deepCopy)