A closure in computer science is the combination of a function and the local scope in which the function was defined. In Roblox, anonymous functions (or local functions) are the main form of closure and are used extensively in event handling code.
This article shows how to define a closure()
function which makes it easier to re-use a common function by pre-defining one or more parameters.
Anonymous Function Example
A closure is automatically created by Roblox whenever an anonymous function is defined. For example, a touch handler can remember the object which is being touched from the context in which the anonymous function was defined. The object which is touching it is passed to the anonymous function as a parameter.
Here we have a function processtouch()
which is passed a touched object and the object which is touching it (and would perform some game logic). An anonymous function captures the obj
variable from the surrounding for
loop and passes it to the pre-defined processtouch()
function.
local function processtouch(obj1,obj2)
print(obj1,"was touched by",obj2)
-- and something useful
end
for _,obj in CollectionService:GetTagged("needtouch") do
obj.Touched:Connect(function(obj2)
processtouch(obj,obj2)
end
end
I often find myself writing repetitive code so I can pull in one reference from the outside scope. The closure()
function defined here can make the code more explicit and allow reuse of a more generic function.
Closure Function
The closure()
function takes a function as its first parameter and one or more additional parameters. It returns a new function which will call the original function with the specified parameters as well as any live parameters that are passed.
function closure(...)
local closed = table.pack(...)
assert(#closed > 0 and type(closed[1]) == "function","closure error")
local func = table.remove(closed,1)
if #closed == 0 then
return func
end
return function(...)
local params = table.pack(...)
if #params > 0 then
table.move(params,1,#params,#closed+1,closed)
end
return func(unpack(closed))
end
end
Now it is simple to create functions that have one or more parameters pre-defined but still accept more parameters when they are called. For example, this function adds a prefix to a print command.
local myprint = closure(print,"Prefix:")
myprint("testing")
The return value of this code shows that both the closed parameter and the live parameter are passed to print()
.
Prefix: testing
Closure Function Example
Going back to the original use case we can now use the closure function to reuse a pre-defined function rather than specifying an anonymous function on each :Connect()
call.
local function processtouch(obj1,obj2)
print(obj1,"was touched by",obj2)
-- and something useful
end
for _,obj in CollectionService:GetTagged("needtouch") do
obj.Touched:Connect(closure(proccesstouch,obj))
end
Other Examples
A set()
function can be bound to a specific object and key.
local function set(obj, key, val)
obj[key] = val
end
local mytable = {one = "value1", two = "value2"}
local setvalone = closure(set,mytable,"one")
...
setvalone("value3")
print(mytable)
The output is
{
one = "value3",
two = "value2"
}
This works on objects as well.
local mybutton = gui:FindFirstChild("MyButton")
local setmybuttonsize = closure(set,mybutton,"Size")
...
setmybuttonsize(Vector2.new(100,25)
Many of the event handlers for gui objects do not pass the input object when they are called. This can make it difficult to re-use code. For example, the Button1Up
event passes the callback the x,y location of the mouse, but not the button which was pressed. With a closure we can include a reference to the button so this works more like Activated
.
function handleclick(button, x, y)
print(button, "clicked at", x, y)
end
local mybutton = gui:FindFirstChild("MyButton")
mybutton.MouseButton1Up:Connect(closure(handleclick, mybutton))