Bienvenidos sean a este post, en el post anterior hemos hablado sobre las metatablas y para que se usan, tambien una muy breve explicacion (brevisima) de que es un metametodo, por eso hoy hablaremos sobre el primero de los metametodos y como esto nos ayuda a implementar metatablas, para ello vamos a utilizar un ejemplo: en este caso vamos a suponer que usamos tablas para representar conjuntos con funciones para computar la union de dos conjuntos, interseccion y similares, y para mantener nuestros espacios de nombre (namespace) limpios almacenaremos estas funciones dentro de una tabla llamada conjunto, veamos el codigo:

Anuncios
Conjunto = {}

function Conjunto.nuevo(l)
        local conjunto = {}
        for _, v in ipairs(l) do conjunto[v] = true end
        return conjunto
end

function Conjunto.union(a, b)
        local res = Conjunto.nuevo{}
        for k in pairs(a) do res[k] = true end
        for k in pairs(b) do res[k] = true end
        return res
end

function Conjunto.interseccion(a, b)
        local res = Conjunto.nuevo{}
        for k in pairs(a) do
                res[k] = b[k]
        end
        return res
end

En este caso primero tendremos una tabla llamada conjunto y sera inicializada, despues las tres funciones basicas para interactuar sobre conjunto:

  • nuevo, se encarga de crear una nueva tabla y en base al valor informado en el argumento l lo ira agregando en conjunto, una vez finalizado devuelve este resultado
  • union, esta se encargara de crear la union entre dos conjuntos informados, para ello primero creara uno local llamado res, y para esto utilizara a la funcion nuevo que vimos antes, luego usara dos bucles for para agregar todos los valores de los conjuntos informados (a y b) para una vez finalizado devolver el resultado
  • interseccion, este se encargara de verificar donde los dos conjuntos se interconectan entre si, para ello al igual que la funcion anterior creara de la misma forma un conjunto local, luego con un bucle for pasara por todos los elementos de a y luego en base a la posicion k pasara el valor almacenado en b en esa posicion, una vez finalizado devolvera el resultado
Anuncios

Para poder seguir trabajando con los ejemplos anteriores agregaremos una funcion para imprimir los conjuntos:

function Conjunto.acadena(set)
        local l = {}
        for e in pairs(set) do
                l[#l + 1] = e
        end
        return "{ " .. table.concat(l,", ") .. " }"
end

function Conjunto.imprimir(c)
        print(Conjunto.acadena(c))
end

En este caso la primer funcion se encargara de convertir el conjunto a cadena, para ello crearemos una tabla llamada l, la cual recibira todos los elementos del conjunto set, esto lo haremos por medio del bucle for donde iremos agregando uno detras del otro gracias al #l, que calcula la cantidad de elementos, y sumado uno, donde e sera el valor en si, una vez terminado el ciclo, usaremos el formato de tabla y para eso primero abriremos una llave, luego concatenaremos con los dos puntos (..) el resultante de la funcion table.concat sobre la tabla l y usaremos el “, ” para crear los elementos individuales por ultimo concatenaremos la llave que cierra la tabla y devolvemos esto gracias al return, nuestra siguiente funcion sera para imprimirlo y para ello imprimiremos el resultado de la funcion anterior, nuestro siguiente paso sera agregar el operador de adicion (+) que nos permitira computar la union de dos conjuntos, para eso organizaremos todas nuestras tablas que representan conjuntos para compartir una metatabla, la cual definira como reaccionan ante el operador de adicion, para ello nuestro primer paso sera crear una tabla normal que la usaremos como metatablas para conjuntos:

local mt = {}

Nuestro siguiente paso sera modificar la funcion conjunto.nuevo de la siguiente manera:

function Conjunto.nuevo(l)
        local conjunto = {}
        setmetatable(conjunto, mt)
        for _, v in ipairs(l) do conjunto[v] = true end
        return conjunto
end

En este caso solo agregamos una linea, la encargada de setear la metatabla (setmetable), donde indicaremos que la metatabla de conjunto sera mt, y sera la metatabla para todas las nuevas que vaya creando, por ultimo agregaremos a mt el campo __add que se encargara de describir como debe ejecutar la adicion:

mt.__add = Conjunto.union
Anuncios

Despues de estas modificaciones vamos a verificar nuestro codigo hasta ahora, primero verifiquemos que se cree la metatabla por cada conjunto creado:

> s1 = Conjunto.nuevo{10, 20, 30, 50}
> s2 = Conjunto.nuevo{30, 1}
> getmetatable(s1)
table: 0x23cb230
> print(getmetatable(s2))
table: 0x23cb230

Primero creamos los dos conjuntos por medio de Conjunto.nuevo y su respectiva informacion, en este caso creamos dos conjuntos (s1 y s2) despues llamamos a getmetatable de dos formas distintas tanto para s1 como para s2 y en ambos obtuvimos la direccion donde esta almacenada, de lo contrario hubiera devuelto nil, para nuestro siguiente caso usaremos el siguiente ejemplo:

> s3 = s1 + s2
> Conjunto.imprimir(s3)
{ 1, 20, 30, 10, 50 }

En este caso podemos ver como al encontrar el signo mas en este chunk lo primero que efectuo el llamado a la funcion union, y produciendo la salida que es la union de los dos conjuntos, de una forma similar vamos a agregar el signo de multiplicacion para llamar a interseccion:

mt.__mul = Conjunto.interseccion

Si lo chequeamos obtendremos este resultado:

> Conjunto.imprimir((s1 + s2) * s1)
{ 20, 50, 30, 10 }
> Conjunto.imprimir((s1 + s2) * s2)
{ 1, 30 }
>

Como podemos observar por cada operador arimetico hay un nombre de campo correspondiente en una metatabla:

  • __add, el ya visto para sumar
  • __mul, para multiplicacion
  • __sub, para restar
  • __div, para dividir
  • __unm, para negacion
  • __mod, para obtener el resto de una division
  • __pow, para hacer una exponenciacion
  • __concat, para concatenar
Anuncios

Hasta aqui si agregamos dos conjuntos no habra inconvenientes en cual metatabla debe utilizar pero si nosotros usaramos una expresion que mezclara dos valores con distintas metatablas como el siguiente ejemplo:

> s = Conjunto.nuevo{1, 2, 3}
> s = s + 8

Cuando buscamos un metametodo en Lua, el lenguaje hace los siguientes pasos:

  • Si el primer valor tiene una metatabla con un campo __add, Lua lo usa como metametodo independiente del segundo valor
  • Si el segundo valor tiene una metatabla con un campo _add, Lua lo usa como metametodo
  • Si alguna de las dos no cumple los requisitos devuelve un error

Por eso el ultimo ejemplo llamara a Conjunto.union pero aunque a Lua no le interesa la mezcla de tipos pero a nuestra implementacion si, si ejecutamos ese ejemplo nos devolvera el siguiente mensaje:

bad argument #1 to 'pairs' (table expected, got number)

Si queremos un mensaje de error mas claro debemos chequear los tipos de operandos explicitamente antes de intentar ejecutar la operacion, para ello agregaremos el siguiente condicional en Conjunto.union:

function Conjunto.union(a, b)
        local res = Conjunto.nuevo{}
        if getmetatable(a)~=mt or getmetatable(b)~=b then
                error("Intentaste 'agregar' un conjunto con un valor no-conjunto", 2)
        end
        for k in pairs(a) do res[k] = true end
        for k in pairs(b) do res[k] = true end
        return res
end

Esta condicion solamente chequea si metatabla de a o b es distinta de mt, si lo probamos veremos una salida semejante a esta:

Intentaste 'agregar' un conjunto con un valor no-onjunto
Nota: el seguno argumento de error, 2 en este caso, direcciona el mensaje de error a donde la operacion fue llamada.
Anuncios

Antes de terminar, veamos como quedo el codigo final:

metamtd01.lua

Conjunto = {}
local mt = {}

function Conjunto.nuevo(l)
        local conjunto = {}
        setmetatable(conjunto, mt)
        for _, v in ipairs(l) do conjunto[v] = true end
        return conjunto
end

function Conjunto.union(a, b)
        local res = Conjunto.nuevo{}
        if getmetatable(a)~=mt or getmetatable(b)~=b then
                error("Intentaste 'agregar' un conjunto con un valor no-conjunto", 2)
        end
        for k in pairs(a) do res[k] = true end
        for k in pairs(b) do res[k] = true end
        return res
end

function Conjunto.interseccion(a, b)
        local res = Conjunto.nuevo{}
        for k in pairs(a) do
                res[k] = b[k]
        end
        return res
end

function Conjunto.acadena(set)
        local l = {}
        for e in pairs(set) do
                l[#l + 1] = e
        end
        return "{ " .. table.concat(l,", ") .. " }"
end

function Conjunto.imprimir(c)
        print(Conjunto.acadena(c))
end

mt.__add = Conjunto.union
mt.__mul = Conjunto.interseccion
Anuncios

En resumen, hoy hemos visto el primero de los metametodos, el metametodo arimetico, las operaciones que podemos hacer, como relacionarlas, como se llaman los campos de las operaciones que podemos usar, y como interpretar un error por un mal uso, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00