Lua é uma poderosa linguagem de programação procedural, orientada a objetos, de tipagem dinâmica, baseada em tabelas associativas e semântica extensível, projetada e implementada no Tecgraf, Grupo de Computação Gráfica da PUC-Rio, em 1993.
Foi projetada para ser uma linguagem de extensão, para que fosse possível que os usuários reconfigurassem aplicações sem necessidade de recompilação das aplicações.
Atualmente é a linguagem de script mais usada para programação de jogos (em segundo lugar está Python).
Uma curiosidade de Lua é não haver um módulo de suporte a números complexos. Vamos então implementar um.
Metatabelas
Em orientação a objetos, o «tipo» de objeto é chamado
classe. Lua trabalha com um conceito ligeiramente diferente:
metatabelas e
metamétodos.
Em Lua, os tipos definidos pelo usuário são sempre tabelas, cujos elementos podem ser quaisquer objetos de primeira ordem, inclusive funções (métodos) e outras tabelas.
Ainda é possível associar uma metatabela a uma tabela: esta metatabela define como a tabela deve comportar-se. Então a metatabela funciona de forma semelhante a classe em outras linguagens.
Os métodos da metatabela definem como a tabela reagirá às operações matemáticas, às comparações e até como responderá quando for solicitado o valor de um elemento elemento que não possui (podemos chamar isso de valores
default).
Para as operações matemáticas temos os métodos
__add()
(adição),
__sub()
(subtração),
__mul()
(multiplicação),
__div()
(divisão) e
__pow()
(potência). Ainda há os métodos
__unm()
(inversão de sinal),
__tostring()
(conversão para string) e
__concat()
(concatenação).
As operações comparativas são
__eq()
(igualdade),
__lt()
(menor que) e
__le()
(menor ou igual). Alguém pode perguntar «
e quanto a ‘maior que’ e ‘maior ou igual’?». A resposta é simples, se
a > b
então
b < a
,
capicci? A mesma idéia é para diferença (
a ~= b
é o mesmo que
not a == b
).
Está confuso até aqui? Vai clarear assim que começarmos a criar nossa metatabela. Vamos então à criação de nossa metatabela: primeiro usaremos o construtor de tabela (
{}
) para criar a metatabela com alguns valores
default e em seguida criaremos o construtor próprio para números complexos:
local cmt = { real = 0, img = 1 }
cmt.__index = cmt
function cmt:new(o)
o = o or {}
setmetatable(o, self)
return o
end
Números complexos são formados por duas partes, uma real e uma imagem, então criamos nossa metatabela assim.
A sintaxe
cmt:new(o)
é um açúcar sintático para
cmt.new(self, o)
e é usada para métodos.
O comando
setmetatable(o, self)
define que a metatabela de
o
será
self
e o elemento
__index
informa de onde a tabela deve retirar os valores
default (para elementos não definidos), e será a própria metatabela.
No entanto queremos ter alguma flexibilidade ao criar um número complexo:
- Se não for passado argumento, queremos que a função retorne i;
- Se for passado um número (real), queremos retornar ele mesmo;
- Se for passado um número complexo, queremos retornar uma cópia dele;
- Se forem passados dois números (reais), queremos que o primeiro seja a parte real e que o segundo seja a imagem (se a imagem for zero, retorne somente a parte real).
Assim sendo, podemos criar a seguinte função:
local function new(...)
if arg.n == 0 then
-- nenhum argumento retorna i
return cmt:new { real = 0, img = 1 }
elseif arg.n == 1 and type(arg[1]) == “number” then
-- um argumento: numero real
return arg[1]
elseif arg.n == 1 and getmetatable(arg[1]) == cmt then
-- um argumento: numero complexo
return cmt:new { real = arg[1].real, img = arg[1].img }
elseif arg.n == 2 and
type(arg[1]) == “number” and type(arg[2]) == “number” then
-- dois argumentos reais
if arg[2] == 0 then
return arg[1]
else
return cmt:new { real = arg[1], img = arg[2] }
end
else
error “parse error”
end
end
Mostrando nosso número complexo
O primeiro método que definiremos será para exibir nosso número complexo.
Sem este método, o comando abaixo retornaria assim:
lua> print(numero)
table: 0x80725e0
E queremos que, na verdade retorne algo do tipo:
lua> print(numero)
3 + 2i
Então precisamos definir o método
__tostring()
. Mas não será tão fácil assim!
Imagine só: precisamos definir pelo menos oito casos diferentes:
- imagem = 0
- real = 0, imagem = 1
- real = 0, imagem = -1
- real ≠ 0, imagem = 1
- real ≠ 0, imagem = -1
- real = 0, imagem ≠ 0
- real ≠ 0, imagem > 0 e imagem ≠ 1
- real ≠ 0, imagem < 0 e imagem ≠ -1
Vamos então!
function cmt:__tostring()
local a, b = self.real, self.img
if b == 0 then
return tostring(a)
elseif b == 1 and a == 0 then
return “i”
elseif b == -1 and a == 0 then
return “-i”
elseif b == 1 and a ~= 0 then
return a .. “ + i”
elseif b == -1 and a ~= 0 then
return a .. “ - i”
elseif b ~= 0 and a == 0 then
return b .. “i”
elseif b > 0 and a ~= 0 then
return a .. “ + “ .. b .. “i”
elseif b < 0 and a ~= 0 then
return a .. “ - “ .. (-b) .. “i”
else
error “unexpected (a + bi) combination: contact author”
end
end
Uma funçãozinha útil
Para definir alguns parâmetros importantes precisamos definir o que acontece com o número quando tentamos invertê-lo (
(a + bi)-1
).
A operação matemática é multiplicar a fração resultante por uma expressão equivalente a 1, como
(a – bi) / (a – bi)
.
Fazendo esta continha simpática, obtemos real
a / (a2 + b2)
e imagem
-b / (a2 + b2)
.
Vamos criar nossa função:
local function inv(v)
if getmetatable(v) ~= cmt then
return v ^ (-1)
else
local a, b, q = v.real, v.img
q = a ^ 2 + b ^ 2
return new(a / q, -b / q)
end
end
Nesta função, a primeira coisa que fizemos foi verificar se o argumento é um número complexo. Se não for, a função retorna um dividido pelo argumento. Se for um número complexo, realiza o cálculo citado.
Repare na linha:
local a, b, q = v.real, v.img
Neste comando,
a
,
b
e
q
são definidos como variáveis locais.
a
recebe
v.real
,
b
recebe
v.img
e
q
recebe
nil
.
Inversão de sinais
Se temos um número complexo
b
, queremos que
-b
retorne um número complexo com os sinais do real e da imagem invertidos:
function cmt:__unm()
return new(-self.real, -self.img)
end
Igualdade
Vamos verificar igualdade. Quando Lua verifica
a == b
, sendo
a
e
b
tabelas, na verdade está verificando se
a
e
b
são exatamente a mesma tabela, não se seus elementos são iguais. O quer queremos quando comparamos dois números complexos é se representam o mesmo valor, ou seja, se seus reais são iguais e se suas imagens também são.
Precisamos então criar um método para tratar isso:
function cmt:__eq(v)
if getmetatable(v) == cmt then
return self.real == v.real and self.img == v.img
else
return self.img == 0 and v == self.real
end
end
Operações binárias
Agora definiremos as operações binárias, ou seja, que necessitam de dois operandos: adição (
+
), subtração (
-
), multiplicação (
*
), divisão (
/
), potência (
^
) e concatenação (
..
).
Em todos os casos verificaremos se o segundo elemento da operação é também um número complexo ou não.
function cmt:__add(v)
local a1, b1, a2, b2, a3, b3 = self.real, self.img
if getmetatable(v) ~= cmt then
a2, b2 = v, 0
else
a2, b2 = v.real, v.img
end
a3, b3 = a1 + a2, b1 + b2
return new(a3, b3)
end
Na declaração das variáveis locais,
a1
recebe
self.real
e
b1
self.img
. Todas as demais variáveis recebem
nil
.
- Subtração (nada mais do que soma com o sinal invertido)
function cmt:__sub(v)
return self + (-v)
end
- Multiplicação (muito semelhante à adição)
function cmt:__mut(v)
local a1, b1, a2, b2, a3, b3 = self.real, self.img
if getmetatable(v) ~= cmt then
a2, b2 = v, 0
else
a2, b2 = v.real, v.img
end
a3, b3 = a1 * a2 – b1 * 2, a1 * b2 + a2 * b1
return new(a3, b3)
end
- Divisão (divisão é a multiplicação onde o segundo termo é invertido)
function cmt:__div(v)
return self * inv(v)
end
- Potência (aqui trataremos apenas expoentes inteiros, que não passam de multiplicações sucessivas)
function cmt:__pow(v)
if v == 0 then
-- expoente 0 retorna 1
return 1
elseif v == 1 then
-- expoente 1 retorna uma cópia de si mesmo
return new(self)
elseif v < 0 then
-- expoente negativo retorna inversao
return inv(self ^ (-v))
else
local aux, cont = new(self)
for cont = 2, v do
aux = self * aux
end
return aux
end
end
Repare a recursividade na linha:
return inv(self ^ (-v))
A potência chama novamente
__pow()
para
self
, mas desta vez com argumento
-v
.
- Concatenação (fácil: retorna a contenação das strings!)
function cmt:__concat(v)
return tostring(self) .. tostring(v)
end
Vamos tornar isso tudo útil?
Até agora está tudo muito bonito, tratando números complexos e tudo mais... mas números complexos só são úteis se pudermos fazer duas coisas:
1converter números reais em complexos quando tentamos extrair a raiz de um número negativo e
2converter números complexos para reais por meio das operações básicas.
Bem, a segunda coisa nosso módulo já faz, falta a primeira! Para tanto, vamos criar uma função de raiz quadrada segura, que retorne um número complexo quando a base for negativa:
local function sqrt(v)
if type(v) ~= “number” then
error “value must be a number”
end
if v >= 0 then
return v ^ .5
else
return (0, (-v) ^ .5)
end
end
Também será útil termos uma função que retorne verdadeiro ou falso para verificar se um valor é um número complexo:
local function iscomplex(v)
return getmetatable(v) == cmt
end
Finalizando
Até aqui, tudo o que fizemos foi privado (
local
). Vamos então criar uma tabela pública onde colocaremos somente as partes que nos interessam:
complex = {
iscomplex = iscomplex,
new = new,
sqrt = sqrt,
i = new()
}
Vamos agora testar! Salve este arquivo como
complex.lua
no diretório atual e execute o interpretador
lua
. Execute os seguintes comandos e veja se funciona:
lua> require “complex”
lua> for c = -4, 7 do print(c, complex.i ^ c) end
-4 1
-3 i
-2 -1
-1 -i
0 1
1 i
2 -1
3 -i
4 1
5 i
6 -1
7 -i
lua> a = complex.sqrt(-9) + 2
lua> b = complex.new(3, 2)
lua> print(a, b)
2 + 3i 3 + 2i
lua> print(a + b)
5 + 5i
lua> print(a * b)
13i
lua> print(a / 2)
1 + 1.5i
lua> print(b ^ 2)
5 + 12i
Se tudo sair direitinho, parabéns! Acabou de fazer seu primeiro módulo de números complexos.
[]'s