Ad
Log in to vote
1

Why won't some metamethods work properly?

Asked by 14 days ago
Edited 14 days 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 2586 Moderation Voter Community Moderator
14 days 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