Ad
Log in to vote
1

Why won't some metamethods work properly?

Asked by 6 months ago
Edited 6 months ago

I'm using metamethods for my "Number Instance" module to act like real numbers. Most of the metamethods are working fine, except for these three: __eq, __le, and __lt. They are scripted the same way with __concat, __add, __sub, __mul, __div, __mod, and __pow, but they won't work properly as expected. I tried printing them in a script and this is what they printed:

local NumberService = require(game:GetService("ReplicatedStorage"):WaitForChild("CustomServices")):GetService("NumberService")

local TestNumber = NumberService.new(50)

print("My number is " .. TestNumber) -- "My number is 50" (working) __concat
print(-TestNumber) -- -50 (working) __unm
print(TestNumber + 50) -- 100 (working) __add
print(60 - TestNumber) -- 10 (working) __sub
print(TestNumber * 2) -- 100 (working) __mul
print(50 / TestNumber) -- 1 (working) __div
print(TestNumber % 28) -- 22 (working) __mod
print(2 ^ TestNumber) -- 1125899906842624 (working) __pow
print(tostring(TestNumber)) -- "50" (working) __tostring
print(50 == TestNumber) -- false (not working) __eq
print(TestNumber < 60) -- attempt to compare table < number (not working) __lt
print(TestNumber > 60) -- attempt to compare table > number (not working) __lt
print(40 <= TestNumber) -- attempt to compare table <= number (not working) __le
print(TestNumber >= 40) -- attempt to compare table >= number (not working) __lt
print(#TestNumber) -- attempt to get length of a number value (working) __len

This is the expected output to the lines that are not working

print(50 == TestNumber) -- true
print(TestNumber < 60) -- true
print(TestNumber > 60) -- false
print(40 <= TestNumber) -- true
print(TestNumber >= 50) -- true

And this is the module script

newNumber.__index = NumberInstance
newNumber.__concat = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number .. b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number .. b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a .. b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a .. b
    end
end
newNumber.__unm = function(self)
    return -(self.Number)
end
newNumber.__add = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number + b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number + b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a + b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a + b
    end
end
newNumber.__sub = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number - b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number - b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a - b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a - b
    end
end
newNumber.__mul = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number * b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number * b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a * b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a * b
    end
end
newNumber.__div = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number / b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number / b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a / b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a / b
    end
end
newNumber.__mod = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number % b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number % b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a % b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a % b
    end
end
newNumber.__pow = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number ^ b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number ^ b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a ^ b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a ^ b
    end
end
newNumber.__tostring = function(self)
    return tostring(self.Number)
end
newNumber.__eq = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number == b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number == b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a == b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a == b
    end
end
newNumber.__lt = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number < b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number < b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a < b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a < b
    end
end
newNumber.__le = function(a, b)
    local isNumberInstance1 = typeof(a):lower() == "table" and a.Number ~= nil
    local isNumberInstance2 = typeof(b):lower() == "table" and b.Number ~= nil

    if isNumberInstance1 and isNumberInstance2 then
        return a.Number <= b.Number
    elseif isNumberInstance1 and not isNumberInstance2 then
        return a.Number <= b
    elseif not isNumberInstance1 and isNumberInstance2 then
        return a <= b.Number
    elseif not isNumberInstance1 and not isNumberInstance2 then
        return a <= b
    end
end
newNumber.__len = function(self)
    error("attempt to get length of a number value", 0)
end

1 answer

Log in to vote
1
Answered by
Xapelize 2658 Moderation Voter Community Moderator
6 months ago

It turns out __eq, _lt, and __le doesn't fire if the other token is a different datatype from the class.

For example:

MyCustomClass == "30"

Let's say you have a backend code that helps you to return the value MyCustomClass.String == "30", it will still not fire because MyCustomClass does not share the same datatype with "30". This rule is shared with the equal, bigger, or less than the comparative operator.

Also, a workaround is to do NumberService.new(50) == TestNumber in your case, so 50 shares the same datatype with TestNumber.

Ad

Answer this question