Request for metamethod __typeof

Possible usage (taken from Metatables (vector2 example)):

Before

local vector2 = {__type = "vector2"}
local mt = {__index = vector2}

...

	elseif (type(b) == "number") then
		-- a is a vector, b is a scalar
		local vector, scalar = a, b
		return vector2.new(vector.x / scalar, vector.y / scalar)
	elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
		-- both a and b are vectors
		return vector2.new(a.x / b.x, a.y / b.y)
...

Notice the type comparison between two vector2’s uses a __type table index

After

local vector2 = {__type = "vector2"}
local mt = {__index = vector2}

...

	function mt.__typeof(t)
        return t.__type
    end

...

	elseif (type(b) == "number") then
		-- a is a vector, b is a scalar
		local vector, scalar = a, b
		return vector2.new(vector.x / scalar, vector.y / scalar)
	elseif (typeof(a) == "vector2" and typeof(b) == "vector2") then
		-- both a and b are vectors
		return vector2.new(a.x / b.x, a.y / b.y)
...

Much cleaner :smiley:

6 Likes

Possibly confusing since one would always be expecting the typeof to return the internal Roblox type. It seems in your specific case it may be easier to implement a :typeof() method to the classes rather than overloading an already present function that you are expecting a specific use from.

4 Likes

I like the idea of this, but I think there should be some sort of hard prefix before it so you can’t impersonate built-in types like “number”, “string”, etc.

Maybe “user_vector2” ?

2 Likes

I thought lua already supports

local mt = {__type = “my class name”}
print(type(setmetatable({},mt)))

Does Roblox not have this too?

__type is only in Lua 5.2. Roblox uses Lua 5.1.

1 Like

this is why I requested this feature, because I don’t want to check

if type(class) == 'table' and class.typeof and class:typeof(x) then

although it could be __type instead of __typeof to match Lua 5.2

Ideally we would add a seperate method which can determine if the type is internal or otherwise. rawtype might be a suitable name.

3 Likes

I don’t think this is necessary. Currently “typeof()” is only 30% slower than “type()” for Lua types, and I’d prefer to keep it that way. Adding new methods would bloat the global environment.

If you need this behavior you can implement it in less than 10 lines:

local function customTypeof(v)
	local t = typeof(v)
	if t == "table" then
		local mt = getmetatable(v)
		return type(mt) == "table" and rawget(mt, "__typeof") or t
	end
	return t
end

local object = setmetatable({}, {__typeof = "CustomTypeExample"})

print("Test:", customTypeof(object))
3 Likes

typeof/type speed is < 1ms up to 10,000 calls. Even on low-end devices, it would take an insane number of calls for any speed difference to be noticeable. If you’re calling typeof so often that type-checking is your bottleneck, you’re doing something very wrong. This seems like premature optimization and not a realistic problem.

3 Likes

There are multiple issues with this check to begin with which makes it a rare case, and it seems what you’re doing is just general bad practice. From looking at this, what I can infer is:

  1. You are passing/having a variable “class” which may be of multiple types despite the name class suggesting it’s either an userdata or table implementation. Essentially, having a variable which can have an arbitrary value of a different type at any given time.
  2. Your classes do not share common inheritance to a base class to provide the methods such as :typeof (such as Roblox does with the Instance class functionality).
  3. Adding to typeof() global may bloat it more than what it is for already, which is again expected to return a Roblox-defined type, and could potentially be used for spoofing internal functionality depending on how it’s implemented (calling a method that expects something with a __type overriden userdata.
2 Likes

It’s important to type-check everything received via RemoteEvents on the server. Right now I get about 17,000/ms for Lua types and 8000/ms for Roblox types.

I make my argument not because typeof() is a bottleneck for me currently, but because it could become a bottleneck for future use-cases if features like these are added.

3 Likes

I find myself wanting the same thing, and I figure I would add in my use case instead of starting a new thread. I agree that this would be a handy addition, particularly to validate the inputs of a function (defensive programming). For example, I am currently writing:

function class:method(arg)
    if typeof(arg) ~= "table" or arg.isA == nil or not arg:isA("Matrix") then 
        error("arg must be a Matrix : Actual " .. typeof(arg))
    end

    --do method
end

There are a few things to note here: I need to first use typeof(arg) to check that the argument has the potential to be an instance of something, I then have to check that the object contains the function that I defined to do the type/instance check (I call it isA to reflect Instance:IsA, the name isn’t too important), and then I can actually do the check that I am wanting to do. In the event that the check fails, I would then get an error saying
arg must be a Matrix : Actual table
Having a __typeof

I’ve always found that verifying types is better done with:

getmetatable(object) == class

Otherwise you can run into all types of issues when two classes define __type as the same value.

I find myself wanting the same thing, and I figure I would add in my use case here instead of starting a new thread. I agree that this would be a handy addition, particularly to validate the inputs of a function (defensive programming stuff). For example, I am currently writing:

function class:method(arg)
    if typeof(arg) ~= "table" or arg.isA == nil or not arg:isA("Matrix") then 
        error("arg must be a Matrix : Actual " .. typeof(arg))
    end

    --do method
end

There are a few things to note here: I need to first use typeof(arg) to check that the argument has the potential to be an instance of something, I then have to check that the object contains the function that I defined to do the type/instance check (I call it isA to reflect Instance:IsA, the name isn’t too important), and then I can actually do the check that I am wanting to do. In the event that the check fails, I would then get an error saying
arg must be a Matrix : Actual table

Having a __typeof metamethod (could be named anything really) would do a few things:

  1. Make the code much cleaner and clearer
function class:method(arg)
    if typeof(arg) ~= "Matrix" then 
        error("arg must be a Matrix : Actual " .. typeof(arg))
    end

    --do method
end
  1. Make validation consistent with Roblox types like Vector2
  2. Add extra information to the error message (say for a made up user-defined object)
    arg must be a Matrix : Actual Vector4
    Adding a field to Vector4 to print out during an error doesn’t necessarily help my case here, since I would have to do an additional check to ensure that the passed argument has that variable (after all, I have no idea what it is). If I was given a Vector2 or nil, arg._typeToPrint would cause an error in and of itself. Just as an illustration of how messy this becomes,
if typeof(arg) ~= "table" or arg.isA == nil or not arg:isA("Matrix") then 
    if typeof(arg) == "table" and arg._typeToPrint ~= nil then
        error("arg must be a Matrix : Actual " .. arg._typeToPrint)
    else
        error("arg must be a Matrix : Actual " .. typeof(arg))
    end
end

Fair point, though is there a way to do that without requiring the class in a module? Take this module for example:

local pathfinder = {}

pathfinder.BFS(arg)
--do the check I would like to do
end

return pathfinder

In this scenario I don’t have the class to compare the metatable of arg against

I partially support this idea although I don’t like the idea of it overriding typeof specifically. The reason for this is it would make sandboxing scripts much harder not to mention malicious scripts can pretend to return types that they aren’t. I think if this came in the form of typeof(value, unsafe) I would be much happier about this. This doesn’t add too much to type out and fixes this issue.

Secondly, I think if this supported a function callback this could get way more use cases. For example, let’s say you have something like this:

local types = {}
local MyClass = {}
setmetatable(types, {
	__index = function(self, index)
		if index == MyClass then
			return "MyClass"
		end
		return "Unknown"
	end
})
function MyClass:ConvertTo(Class)
	types[self] = typeof(Class, true).."()" -- Now it's an instance!
	Class:ConvertFrom(self)
end
setmetatable(MyClass, {
	__typeof = function(self)
		return types[self]
	end
})
1 Like