How does roblox go about overloading their constructors?

For those who don’t know what function overloading is, it’s changing the behaviour of a function usually based on argument types/ or even argument count.

Here’s a quick C++ example:

#include <iostream>

void test(int a, int b, int c) {
    std::cout << a*b*c;
}

void test() {
    std::cout << "No args passed\n";
}

int main() {
    test(9, 8, 2); // outputs 144
    test(); // outputs No args passed
}

It doesn’t redefine the function or anything, it just adds an overload. When called with three arguments here, it will multiply all three and output the result. When called with none, it outputs No args passed

Back to my question

Since you can’t overload functions the same way in Lua, I’m wondering what’s a decent way to go about it.

To replicate function overloading in Lua, since there’s no true overloading, you’d probably have to do type checks and stuff which I would like to avoid.

local function test(a, b, c)
    if a == nil and b == nil and c == nil then
        print("No args passed") -- let's just assume nil wasn't explicitly passed
        return
    end
    print(a*b*c)
end

test() -- No args passed
test(9, 8, 2) -- 144

If you go to the api reference page for CFrame you will see that the constructor CFrame.new has six overloads. I’m curious how roblox does it. I imagine roblox doing this overloading in a smart way. Literally the longest one is 12 arguments, so how does roblox account for ones that take vectors, and ones that take less arguments?

5 Likes

Perhaps I misunderstand how a function works, but:

Wouldn’t this also print a*b*c anyways?

You’re right, I forgot to return so it doesn’t reach that

1 Like

Cool! Also, thanks for the explanation on what overloading is – I keep seeing the term but never understood what it was.

Roblox functions are generally implemented in C++, so they can overload like you did in the first example. That’s almost definitely what they do with the CFrame library. Ignore this, see @Autterfly’s reply.

Edit: Rodux, a project on the official Roblox Github account, uses a type-checking system similar to your approach. The most obvious example of this is in their thunkMiddleware.lua file. This is a bit of an old project, but it doesn’t seem like they do anything particularly unusual.

Here’s the code they used:
https://raw.githubusercontent.com/Roblox/rodux/master/lib/thunkMiddleware.lua

local function thunkMiddleware(nextDispatch, store)
	return function(action)
		if typeof(action) == "function" then
			return action(store)
		else
			return nextDispatch(action)
		end
	end
end

return thunkMiddleware
4 Likes

As posatta said, the overloading that you describe in the API was implemented through C++.

For the multiplication function you mentioned above, your best bet would be to either pass a table {1, 2, 3, + …} or accept a tuple parameter. Although, as you mentioned, this really only works if you know the types of the arguments you pass in. Since we aren’t strongly typed (yet), overloading function parameters like in c++ just doest exist.

You can overload arithmetic operators using metamethods. This is helpful if you are using metatables in some sort of OOP implementation.

3 Likes

These are both wrong. Function overloading in C++ takes place at compile time and is a static process.

Lua constructors are most definitely overloaded by checking how many arguments were passed (top of stack minus base of stack) and their types.

5 Likes

A really hacky method is you could write a small library for doing this. (lol)

Here is a messy example that I didn’t test but probably illustrates the concept:

local Function = {}

function Function.new(...)
    local groups = {...}
    local calls = {}

    for i, v in pairs(groups) do
        calls[v[1]] = v[2]
    end

    local function new(...)
        local args = {...}
        local callback = calls[#args]

        assert(callback, "Unexpected Variable Count")

        return callback(...)
    end

    return new
end

local function test1(a, b, c)
    return a * b * c
end

local function test2()
    return "noo"
end

local specialFunction = Function.new({3, test1}, {0, test2})

print(specialFunction(1, 2, 3))
print(specialFunction())

Of course then you could add automatic type checking if you were to expand upon this example.

Perhaps Function.new({argCount, {"arg1Type", "arg2Type", "arg3Type"}, callback}, ...)

Should output:

6
noo

Of course this is pretty hacky and you probably do not want to use something like this. But for the time being it looks “nice” and works fine.

You will probably just want to do what @idkhowtocode referenced about accepting a tuple parameter.

 local function a(...)
	local args = {...}

	if #args == 3 then
		local a, b, c = unpack(args)

		return a * b * c
	elseif #args == 0 then
		return "bad food"
	end
end
1 Like

Bumping to reuse the thread. I feel guilty of bumping even though it’s really the same topic: overloading. Sorry.

My goal

I want to know a (cleaner) way of overloading functions

My use case

Some Roblox datatypes are immutable, like Vector3, UDim2, etc. so I want to make a wrapper that makes them “mutable”.

So you wouldn’t need to do this:

local pos = Vector3.new(30, 0, 5)
pos = Vector3.new(pos.X + 50, 0, 0)

You could instead do

local pos = MutableVector3(Vector3.new(30, 0, 5))
pos.X = pos.X + 50

I am thinking of making this into a constructor like

local function MutableVector3(v3) -- construct a MutableVector3 of a regular Vector3

but I would like to implement overloads:


  • MutableVector3() - identity
  • MutableVector3(x, y, z) - construct a new one out of given coordinates

Maybe not so useful but I’d still like to give it a go.

I guess I could do it based on argument count using select("#", ...) alone since no overloads here would use the same argument count but different types. But what if this is needed somewhere else?

With typed Lua in the works, I believe function overloading should be more possible.

This would defy the core aspect of Lua functions being a first class type. If you “overloaded” a function with 2 different types, and then tried attempting to set another variable to the function, it would be ambiguous as to which function you’re referring to. It’s much easier to just use argument count and types (as is internally done).

3 Likes

Well then, I guess it is what it is.

Could you give me a visual?

Would it be like this?

-- let's just assume this would actually work for overloads
local function test()
    print("no argssss")
end

local function test(arg)
    print(arg*7)
end

local test2 = test -- how would it be ambiguous if this is what you meant? 
-- wouldn't it just follow the same rules since it points to same function? 
-- orwould it be a specific overload?

Also @eLunate gave me a pretty clean solution

local overload_dispatch = {
  function(arg1) -- fn/1
    print('a', arg1);
    return 1
  end,
  function(a1, a2) -- fn/2
    print('b', a1, a2);
    return 2
  end
}
local function overloaded_fn(...)
  local n = select('#',...);
  return assert(overload_dispatch[n], "Bad arg count")(...)
end
1 Like

In normal Lua, what you’re doing is creating a new instance of the function (a LClosure) and binding it to a variable.

local test; test = function() print('no args') end
local test; test = function(arg) print(arg * 7) end

This is essentially what you’re doing. There’s no “same rules” to follow unless you rewrite Lua function Closures to be able to hold multiple function definitions, which could add more confusion and overhead for the runtime type checking or argument number comparisons that have to take place.

It honestly adds a weird layer of indirection not desired by a scripting language.

1 Like

Now that I think of it, if the syntax for overloading was the same it could be ambiguous to wanting to point to a new function. that’s probably what you were saying i probably missed it/didn’t interpret it like so

Guess I will base my overloads on argument count and not end up in a situation where I need the same count for different types!
Thank you!