0Pricing
Lua Academy · Урок

Арифметические метаметоды

Перегружайте операторы +, -, *, / и другие операторы с помощью метаметодов.

«Арифметические метаметоды» — бесплатный урок Lua Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Lua Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Lua Academy содержит 4 уроков всего.

Имена метаметодов

Lua определяет метаметоды для всех арифметических операторов. Когда Lua видит арифметическую операцию над таблицей, он ищет соответствующий метаметод в метатаблице этой таблицы. Если оба операнда являются таблицами, Lua сначала проверяет метатаблицу левого операнда.

-- Operator -> Metamethod
-- a + b    -> __add
-- a - b    -> __sub
-- a * b    -> __mul
-- a / b    -> __div
-- a % b    -> __mod
-- a ^ b    -> __pow
-- a // b   -> __idiv
-- -a       -> __unm (unary minus)
-- #a       -> __len
print("Metamethods defined for all Lua operators")

__add для сложения векторов

Определите __add для таблицы-вектора, чтобы поддержать оператор +. Метаметод получает два операнда и возвращает новое значение. Оба операнда могут быть таблицами, либо один из них может быть числом — метаметод должен обрабатывать оба случая.

local Vec = {}
Vec.__index = Vec

Vec.__add = function(a, b)
  return setmetatable({x=a.x+b.x, y=a.y+b.y}, Vec)
end

Vec.__tostring = function(v)
  return string.format("Vec(%g,%g)", v.x, v.y)
end

function Vec.new(x,y) return setmetatable({x=x,y=y},Vec) end

local v1 = Vec.new(1,2)
local v2 = Vec.new(3,4)
print(tostring(v1 + v2))   -- Vec(4,6)

__sub __mul __div

Аналогичным образом определите __sub, __mul и __div. Для умножения обработайте случаи table*table и table*number, проверяя типы операндов внутри метаметода.

local Vec = {}
Vec.__index = Vec
local function V(x,y) return setmetatable({x=x,y=y},Vec) end
Vec.__sub = function(a,b) return V(a.x-b.x, a.y-b.y) end
Vec.__mul = function(a,b)
  if type(a)=="number" then return V(a*b.x, a*b.y)
  elseif type(b)=="number" then return V(a.x*b, a.y*b)
  else return a.x*b.x + a.y*b.y  -- dot product
  end
end
Vec.__tostring=function(v) return "("..v.x..","..v.y..")" end

local v = V(3,4)
print(tostring(v * 2))    -- (6,8)
print(tostring(V(1,0) - V(0,1)))  -- (1,-1)

Унарный минус __unm

__unm — метаметод унарного минуса, вызываемый при вычислении Lua выражения -obj. Он получает операнд (для единообразия одно и то же значение передаётся дважды) и возвращает значение с противоположным знаком.

local Vec = {}
Vec.__index = Vec
local function V(x,y) return setmetatable({x=x,y=y},Vec) end
Vec.__unm = function(a) return V(-a.x, -a.y) end
Vec.__tostring = function(v)
  return string.format("(%g,%g)", v.x, v.y)
end

local v = V(3, -4)
print(tostring(-v))    -- (-3,4)
print(tostring(-(-v))) -- (3,-4)

__len для пользовательской длины

__len вызывается при вычислении #obj для таблицы без необработанной целочисленной последовательности. Определите его, чтобы возвращать осмысленную «длину» для таких типов, как множества, очереди или взвешенные контейнеры.

local Bag = {}
Bag.__index = Bag
Bag.__len = function(b) return b._count end

function Bag.new()
  return setmetatable({_count=0, _items={}}, Bag)
end
function Bag:add(item, qty)
  self._items[item] = (self._items[item] or 0) + (qty or 1)
  self._count = self._count + (qty or 1)
end

local bag = Bag.new()
bag:add("apple", 3)
bag:add("banana", 2)
print(#bag)   -- 5

__mod и __pow

__mod обрабатывает оператор %, а __pow — оператор ^. Они используются реже, но полезны для пользовательских числовых типов, таких как большие целые числа, дроби или объекты модульной арифметики.

local Modular = {}
Modular.__index = Modular
local M = 1000000007

local function mod(n)
  return setmetatable({n = n % M}, Modular)
end

Modular.__add = function(a,b) return mod(a.n + b.n) end
Modular.__mul = function(a,b) return mod(a.n * b.n) end
Modular.__pow = function(a,b)
  local r, base, exp = mod(1), a, b
  while exp > 0 do
    if exp%2==1 then r = r*base end
    base = base*base; exp = math.floor(exp/2)
  end
  return r
end

print((mod(2) ^ 30).n)   -- 73741817 (2^30 mod 1e9+7)

__concat

__concat срабатывает для оператора .., если хотя бы у одного операнда есть этот метаметод. Это позволяет определить конкатенацию для пользовательских типов или создавать плавные предметно-ориентированные языки.

local Builder = {}
Builder.__index = Builder
Builder.__concat = function(a, b)
  local result = {}
  for _, v in ipairs(a._parts) do result[#result+1] = v end
  if type(b) == "string" then result[#result+1] = b
  else for _, v in ipairs(b._parts) do result[#result+1] = v end end
  return setmetatable({_parts=result}, Builder)
end
Builder.build = function(b) return table.concat(b._parts) end

local function S(s) return setmetatable({_parts={s}},Builder) end
print((S("Hello") .. ", " .. S("World") .. "!"):build())

Метаметоды сравнения

__eq определяет == для таблиц (вызывается только тогда, когда у обоих операндов одна и та же метатаблица). __lt определяет <, а __le — <=. Замечание: ~= использует __eq, > использует __lt с переставленными аргументами, а >= использует __le.

local Vec = {}
Vec.__index = Vec
local function V(x,y) return setmetatable({x=x,y=y},Vec) end

Vec.__eq = function(a,b) return a.x==b.x and a.y==b.y end
Vec.__lt = function(a,b)
  return (a.x^2+a.y^2) < (b.x^2+b.y^2)  -- by magnitude
end

print(V(1,2) == V(1,2))  -- true
print(V(1,2) == V(1,3))  -- false
print(V(1,0) < V(2,0))   -- true (1 < 2)

Цепочки метаметодов

Поскольку метаметоды возвращают значения (обычно новые таблицы), операции можно естественным образом объединять в цепочки. Каждый оператор возвращает новый объект, а следующий оператор использует этот результат. Это позволяет создавать плавные арифметические выражения для пользовательских типов.

local Vec = {}
Vec.__index = Vec
local function V(x,y) return setmetatable({x=x,y=y},Vec) end
Vec.__add = function(a,b) return V(a.x+b.x,a.y+b.y) end
Vec.__mul = function(a,b)
  if type(b)=="number" then return V(a.x*b,a.y*b) end
  return V(a.x*b.x+a.y*b.y, 0)   -- simplified
end
Vec.__tostring=function(v) return "("..v.x..","..v.y..")" end

local result = V(1,0) + V(0,1) * 3
print(tostring(result))   -- (1,3)

Операции со смешанными типами

В записи vec * 2 левый операнд является таблицей. В записи 2 * vec левый операнд является числом (у него нет метаметода), поэтому Lua проверяет метатаблицу правого операнда на наличие __mul. Обрабатывайте оба порядка операндов в своём метаметоде.

local Vec = {}
Vec.__index = Vec
local function V(x,y) return setmetatable({x=x,y=y},Vec) end
Vec.__tostring = function(v) return "("..v.x..","..v.y..")" end

Vec.__mul = function(a, b)
  if type(a) == "number" then
    return V(a*b.x, a*b.y)   -- scalar * vec
  elseif type(b) == "number" then
    return V(a.x*b, a.y*b)   -- vec * scalar
  end
  return a.x*b.x + a.y*b.y  -- dot product
end

print(tostring(V(1,2) * 3))  -- (3,6)
print(tostring(3 * V(1,2)))  -- (3,6)

Быстрая проверка

Какой метаметод срабатывает для -myTable?

Повторение: арифметические метаметоды

Краткое содержание:

  • __add __sub __mul __div __mod __pow __idiv — бинарная арифметика
  • __unm — унарный минус; __len — оператор #
  • __concat — оператор ..
  • __eq __lt __le — операторы сравнения
  • Обрабатывайте смешанные типы (число/таблица) внутри метаметодов

Часто задаваемые вопросы

Урок «Арифметические метаметоды» бесплатный?

Да — полный текст урока «Арифметические метаметоды» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Lua Academy, подпишись на CoddyKit PRO. Курс Lua Academy содержит 4 уроков всего.

Чему я научусь в уроке «Арифметические метаметоды»?

Перегружайте операторы +, -, *, / и другие операторы с помощью метаметодов. Ты практикуешь Lua Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Lua Academy?

Предыдущий опыт не требуется. Lua Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Арифметические метаметоды»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Lua Academy?

Да. Каждый урок Lua Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Введение в метатаблицы
  2. __index и __newindex
  3. Арифметические метаметоды
  4. __tostring и __len
← Назад к Lua Academy