luaoperatorslua-tableevaluation

# Using metatables for adding two different types

I have fractions table which stores fractions and fractions get associated with its metatable. `fraction.add` is metaoperation (`__add`). Here is the problem. I want to evaluate x + y.

If x and y are both fractions then there is no issue in evaluating it. It gets evaluated with`farction.add`.

If both x and y are numbers, which are not fractions, then it is simple addition of numbers, which has nothing to do with fractions.

If x is fraction and y is non-fraction number (or vice-versa), then I want to convert fraction x into number and add it to y. I of course can easily convert fraction into number, but the problem is with `__add` metatable of fraction which is associated with `fraction.add`. It works only when both x and y are fractions. It throws error as y is not fraction. So I am looking for the logic that will handle the case when one is fraction and other is non-fraction number. It probably is simple if else in metatable, but I am not able to do it. Any ideas would really be helpful.

Solution

• I've implemented a full-fledged such `fraction` "class" with support for this. It's as simple as checking the types of the arguments `a, b` of the `__add` metamethod and converting them to fractions if needed.

Or, for a more minimal example, which skimps on a couple points:

• Fractions aren't shortened. Float / integer imprecisions / overflows will ultimately result in inaccurate fractions. Ideally you should use some kind of big integers for numerator and denominator, and shorten after every operation.
• For simplicity, only integers will be converted to fraction; floats will raise an error, since converting floats to fractions is more tricky.
• It's missing plenty of operations.
``````local fraction_mt = {}

local function new_fraction(numerator, denominator)
assert(numerator % 1 == 0)
assert(denominator % 1 == 0)
return setmetatable({numerator = numerator, denominator = denominator}, fraction_mt)
end

-- Note: No care is taken to shorten the resulting fraction here.
return new_fraction(a.numerator * b.denominator + b.numerator * a.denominator,
a.denominator * b.denominator)
end

if type(a) == "number" then
a = new_fraction(a, 1)
elseif type(b) == "number" then
b = new_fraction(b, 1)
end
end

function fraction_mt:__tostring()
return ("%d/%d"):format(self.numerator, self.denominator)
end

print(new_fraction(1, 2) + new_fraction(1, 2)) -- 4/4
print(new_fraction(1, 2) + 1) -- 3/2
print(1 + new_fraction(1, 2)) -- 3/2
``````

Note that Lua will call `__add` even if only one of the operands is not a number (see the reference manual):

If any operand for an addition is not a number, Lua will try to call a metamethod. It starts by checking the first operand (even if it is a number); if that operand does not define a metamethod for __add, then Lua will check the second operand. If Lua can find a metamethod, it calls the metamethod with the two operands as arguments, and the result of the call (adjusted to one value) is the result of the operation. Otherwise, if no metamethod is found, Lua raises an error.

After clarification: To convert a fraction to a number before adding, producing a number for `fraction + number` or `number + fraction`, simply use:

``````function fraction_mt:tonumber()
return self.numerator / self.denominator
end