--- filename: learnlua-es.lua contributors: - ["Tyler Neylon", "http://tylerneylon.com/"] translators: - ["Jorge Diaz", "https://github.com/jorgeldb"] --- ```lua -- Dos guiones inician un comentario de una única línea. --[[ Añadir dos corchetes [ y ] lo convierten en un comentario multi-línea --]] ---------------------------------------------------- -- 1. Variables y control de flujo. ---------------------------------------------------- num = 42 -- Todos los números son flotantes -- de precisión doble (64 bits). -- Los dobles de 64 bits pueden tienen -- 52 bits para representación de valores -- enteros, así que no representa un -- problema para valores menores a 52 bits. s = 'alternados' -- Los string son imnutables, como en Python t = "Las comillas dobles también son válidas" u = [[ Los corchetes dobles inician y terminan strings de múltiples líneas. ]] t = nil -- Vuelve a t indefinido. Lua hace uso de Garbage Collector. -- Los bloques se denotan con palabras claves como "do" o "end" -- Ciclo while (do/end) while num < 50 do num = num + 1 -- No existen operadores como ++ o += end -- Sentencia if (then/end) if num > 40 then print('mayor a 40') elseif s ~= 'alternados' then -- ~= significa "diferente de" -- == significa "igual a". Puede usarse en strings, igual que en Python io.write('no mayor a 40\n') -- Por defecto, escribe -- a la salida estándar stdout else -- Las variables son globales por defecto estoEsGlobal = 5 -- Es común utilizar Camel Case. -- Se usa la palabra clave 'local' para declarar variables locales local line = io.read() -- Lee la próxima línea de la entrada -- estándar stdin -- Para concatenar strings se usa el operador ".." print('Viene el invierno, ' .. line) end -- Las variables indefinidas retornan nil -- Esto no es un error foo = unaVariableDesconocida -- Ahora foo = nil. unValorBooleano = false -- Sólo 'nil' y 'false' son valores falsos. ¡0 y "" son verdaderos! if not unValorBooleano then print('era falso') end -- 'or' y 'and' son operadores corto-circuito -- Esto es similar al operador ternario en C/JavaScript ans = unValorBooleano and 'sí' or 'no' --> 'no' karlSum = 0 -- El rango es inclusivo, esto empieza en 1 y termina en 100 for i = 1, 100 do karlSum = karlSum + i end -- Se puede usar "100, 1, -1" con paso negativo como rango decremental fredSum = 0 for j = 100, 1, -1 do fredSum = fredSum + j end -- En general, los rangos son: inicio, fin[, paso]. -- Otra manera de hacer bucle, similar a una sentencia do/while en C/Java repeat print('el camino del futuro') num = num - 1 until num == 0 ---------------------------------------------------- -- 2. Funciones. ---------------------------------------------------- -- Las funciones se declaran con "function" function fib(n) if n < 2 then return 1 end return fib(n - 2) + fib(n - 1) -- ¡Pueden ser recursivas! end -- Las clausuras y funciones anónimas están permitidas: function adder(x) -- La función retornada es creada al invocar "caller" -- y recuerda el valor de x. return function (y) return x + y end end a1 = adder(9) a2 = adder(36) print(a1(16)) --> 25 print(a2(64)) --> 100 -- Los retornos, llamados de función y asignaciones -- admiten listas que pueden ser diferentes en -- tamaño. -- Los receptores sin valor asociado son nil. -- Los valores sin receptores son descartados. x, y, z = 1, 2, 3, 4 -- Ahora, x = 1, y = 2, z = 3. El 4 es descartado. function bar(a, b, c) print(a, b, c) return 4, 8, 15, 16, 23, 42 end x, y = bar('zaphod') --> Esto imprime "zaphod nil nil" -- Ahora x = 4, y = 8, y los valores 15, 16, 23 y 42 son descartados. -- Las funciones son de primera clase, pueden ser globales o locales: -- Estas 2 líneas hacen lo mismo: function f(x) return x * x end f = function (x) return x * x end -- Al igual que estas 2 líneas: local function g(x) return math.sin(x) end local g; g = function (x) return math.sin(x) end -- La declaración 'local g' hace que las autorreferencias de g sean válidas -- Por cierto, las funciones trigonométricas trabajan en radianes. -- Los llamados de funciones con un único string no requieren paréntesis. -- Estas 2 líneas de código hacen lo mismo: print 'hello' print('hello') ---------------------------------------------------- -- 3. Tablas. ---------------------------------------------------- -- Las tablas son la única estructura de datos compuesta: -- Son arreglos asociativos. -- De manera similar a los arreglos de PHP u objetos de JS, -- son diccionarios de búsqueda de hash que también pueden -- ser usados como listas. -- Usando tablas como diccionarios / mapas: -- Los literales de diccionarios usan strings como llaves por defecto: t = {key1 = 'value1', key2 = false} -- Se puede acceder a 'key1' usando corchetes '[' y ']': print(t['key1']) -- => 'value1' -- Las llaves tipo string pueden usar notación de punto como JS: print(t.key1) -- Imprime 'value1'. t.newKey = {} -- Añade un nuevo par llave/valor t.key2 = nil -- Elimina key2 de la tabla -- Cualquier literal no nulo puede ser una llave: u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'} print(u[6.28]) -- imprime "tau" -- La correspondencia de llave es por valor para números -- y strings, pero es por identidad para tablas. a = u['@!#'] -- 'a' tiene el valor 'qbert' b = u[{}] -- 'b' tiene valor nil -- 'b' es nil debido a que la búsqueda falló. Esta -- búsqueda falla porque la llave que usamos es un -- objeto diferente al que usamos para crear la llave -- original. Los números y strings son llaves más portables -- para este propósito. -- Una llamada de función con un único parámetro tipo tabla no -- requiere paréntesis. function h(x) print(x.key1) end h{key1 = 'Sonmi~451'} -- Imprime 'Sonmi~451'. for key, val in pairs(u) do -- Iteración llave/valor sobre una tabla print(key, val) end -- _G es una tabla especial para todos los globales print(_G['_G'] == _G) -- Imprime 'true'. -- En este caso, la variable global t se puede consultar de esta manera t = 6 print(_G['t']) -- Imprime '6' -- Usando tablas como listas / arreglos: -- Las listas de literales usan implícitamente enteros como llaves v = {'value1', 'value2', 1.21, 'gigawatts'} for i = 1, #v do -- #v es el tamaño de la lista v print(v[i]) -- Los índices inician en 1. ¡Qué locura! end -- No existe un tipo de dato "Lista". v es sólo una -- tabla con llaves enteras consecutivas. ---------------------------------------------------- -- 3.1 Metatablas y Metamétodos. ---------------------------------------------------- -- Una tabla puede tener una metatabla que otorga a la tabla -- comportamientos similares a sobrecarga de operadores. Más -- tarde veremos cómo las metatablas soportan el comportamiento -- de prototipos de JavaScript. f1 = {a = 1, b = 2} -- Representa la fracción a / b f2 = {a = 2, b = 3} -- Esto puede fallar: -- s = f1 + f2 metafraction = {} function metafraction.__add(f1, f2) sum = {} sum.b = f1.b * f2.b sum.a = f1.a * f2.b + f2.a * f1.b return sum end setmetatable(f1, metafraction) setmetatable(f2, metafraction) s = f1 + f2 -- Esto llama la función __add(f1, f2) de la metatabla -- f1 y f2 no tienen llave para su metatabla, a diferencia -- de los prototipos de JS, así que se debe recuperar usando -- getmetatable(f1). La metatabla es sólo una tabla normal con -- llave que Lua reconoce, como "__add". -- Pero la siguiente línea falla ya que s no tiene metatabla. -- t = s + s -- Los patrones tipo clase a continuación solucionan ese problema. -- Una llave __index en una metatabla sobrecarga las consultas de punto: defaultFavs = {animal = 'gru', food = 'donuts'} myFavs = {food = 'pizza'} setmetatable(myFavs, {__index = defaultFavs}) eatenBy = myFavs.animal -- ¡Funciona! Gracias, metatabla. -- Las consultas a la tabla que fallen serán reintentadas -- en el valor __index de la metatabla, de manera recursiva. -- Un valor __index también puede ser una function(tbl, key) -- para consultas más avanzadas. -- Los valores de __index, __add... son llamados metamétodos. -- Acá hay una lista completa con los metamétodos: -- __add(a, b) para a + b -- __sub(a, b) para a - b -- __mul(a, b) para a * b -- __div(a, b) para a / b -- __mod(a, b) para a % b -- __pow(a, b) para a ^ b -- __unm(a) para -a -- __concat(a, b) para a .. b -- __len(a) para #a -- __eq(a, b) para a == b -- __lt(a, b) para a < b -- __le(a, b) para a <= b -- __index(a, b) para a.b -- __newindex(a, b, c) para a.b = c -- __call(a, ...) para a(...) ---------------------------------------------------- -- 3.2 Tablas como clases y herencia. ---------------------------------------------------- -- Aunque las clases no están incorporadas, existen maneras -- diferentes de hacerlas usando tablas y metatablas. -- La explicación de este ejemplo está justo debajo: Dog = {} -- 1. function Dog:new() -- 2. newObj = {sound = 'woof'} -- 3. self.__index = self -- 4. return setmetatable(newObj, self) -- 5. end function Dog:makeSound() -- 6. print('I say ' .. self.sound) end mrDog = Dog:new() -- 7. mrDog:makeSound() -- 'I say woof' -- 8. -- 1. Dog actúa como una clase, aunque es sólo una tabla -- 2. function table:fn(...) es lo mismo que -- function table.fn(self, ...) -- El operador ':' añade un primer argumento llamado self. -- Lea 7 y 8 para entender cómo self obtiene su valor. -- 3. newObj será una instancia de clase Dog -- 4. self = la clase siendo instanciada. Usualmente, -- self sería Dog, pero la herencia puede cambiar eso. -- newObj obtiene las funciones de self cuando establecemos -- la metatabla e __index de newObj a self. -- 5. Recordatorio: setmetatable retorna su primer argumento. -- 6. El operador ':' funciona igual que en 2, pero esta vez -- esperamos que self sea una instancia de la clase. -- 7. Lo mismo que Dog.new(Dog), por lo tanto self = Dog en new(). -- 8. Lo mismo que mrDog.makeSound(mrDog), self = mrDog. ---------------------------------------------------- -- Ejemplo de herencia: LoudDog = Dog:new() -- 1. function LoudDog:makeSound() s = self.sound .. ' ' -- 2. print(s .. s .. s) end seymour = LoudDog:new() -- 3. seymour:makeSound() -- 'woof woof woof' -- 4. -- 1. Loud dog obtiene los métodos y variables de Dog -- 2. self tiene una llave 'sound' obtenido de new(), ver 3. -- 3. Lo mismo que LoudDog.new(LoudDog), y convertido a -- Dog.new(LoudDog) ya que LoudDog no tiene llave 'new', -- pero tiene __index = Dog en su metatabla. -- Resultado: La metatabla de seymour es LoudDog, y -- LoudDog.__index = LoudDog. Así que seymour.key -- = seymour.key, LoudDog.key o Dog.key, dependiendo de -- cuál tabla sea la primera con la llave dada. -- 4. La llave 'makeSound' se encuentra en LoudDog: -- Es lo mismo que LoudDog.makeSound(seymour). -- Si es requerido, el 'new()' de una subclase es igual -- al de la clase base. function LoudDog:new() newObj = {} -- set up newObj self.__index = self return setmetatable(newObj, self) end ---------------------------------------------------- -- 4. Módulos. ---------------------------------------------------- --[[ Comento esta sección del código para que el resto del script siga siendo ejecutable ``` ```lua -- Supongamos que el archivo mod.lua se ve así: local M = {} local function sayMyName() print('Hrunkner') end function M.sayHello() print('Why hello there') sayMyName() end return M -- Otro archivo puede usar las funcionalidades de mod.lua local mod = require('mod') -- Corre el archivo mod.lua -- 'require' es la función estándar para incluir módulos -- 'require' funciona así (si no ha sido almacenado en caché, ver abajo) local mod = (function () end)() -- Es como si mod.lua fuese el cuerpo de una función, de tal manera -- que los locales de mod.lua son invisibles fuera de él. -- Esto funciona porque mod es igual a M dentro de mod.lua mod.sayHello() -- Imprime: Why hello there Hrunkner -- Esto es erróneo. sayMyName sólo existe dentro de mod.lua: mod.sayMyName() -- error -- El valor de 'require' es guardado en caché, así que cada archivo -- se ejecuta máximo una vez, incluso si se usa 'require' varias veces. -- Suponga que mod2.lua contiene "print('Hi!')" local a = require('mod2') -- Imprime 'Hi!' local b = require('mod2') -- No imprime. También, a = b -- 'dofile' es similar a require pero no usa caché. dofile('mod2.lua') --> Hi! dofile('mod2.lua') --> Hi! (lo ejecuta nuevamente) -- 'loadfile' carga un archivo lua, pero no lo ejecuta f = loadfile('mod2.lua') -- Se puede llamar f() para ejecutarlo. -- 'load' es como 'loadfile' para strings que contengan código lua -- ('loadstring' es obsoleto, se prefiere el uso de 'load') g = load('print(343)') -- Retorna una función g() -- Imprime '343', nada es impreso antes de esto. --]] ``` ## Referencias Estaba emocionado por aprender lua para poder crear juegos con el motor de juegos [LÖVE](http://love2d.org/). Ese es el por qué. Empecé con [BlackBulletIV para programadores Lua](https://ebens.me/posts/lua-for-programmers-part-1/). Luego, leí el libro oficial de [Programación en Lua](http://www.lua.org/pil/contents.html). Ese es el cómo. Podría serle útil darle un vistazo a [Lua Short Reference](http://lua-users.org/wiki/LuaShortReference) en lua-users.org. Los principales temas no cubiertos son las librerías estándar: * [Librería de strings](http://lua-users.org/wiki/StringLibraryTutorial) * [Librería de tablas](http://lua-users.org/wiki/TableLibraryTutorial) * [Librería de matemáticas](http://lua-users.org/wiki/MathLibraryTutorial) * [Librería de Entrada/Salida (`io`)](http://lua-users.org/wiki/IoLibraryTutorial) * [Libreria de Sistema Operativo (`os`)](http://lua-users.org/wiki/OsLibraryTutorial) Por cierto, el archivo entero es código Lua válido. ¡Guárdelo como aprendiendo.lua y ejecútelo con el comando "`lua aprendiendo.lua`" ! ¡Que se divierta con lua!