Finding Closure

So first I’m going to assume you know a little bit about functions and anonymous functions. If you don’t, here’s a little crash course:

In Lua, a function can be stored in a variable. In fact, function names are technically just variable names. For example:

function A()
    print(":)")
end
B = A
A()
B()
A = 2
print(A)
B()

Output:

:) 
:) 
2
:)

In fact, the syntax you most commonly see to define a function

function ThisIsTheFunctionName(a,b,c)
    return a*b*c
end

Is really just shorthand for

ThisIsTheFunctionName = function(a,b,c)
    return a*b*c
end

In fact, you can use a function anywhere you would put a variable: As an argument to another function (which is how events work), you can pass them around, redefine them, and even return them!
What’s a closure
Closures hail from the land of functional programming: descendant from hardcore mathematics. As such, it’s easiest to introduce them with fairly mathematical examples. Let’s take a look at some lua code.

function AddX(x)
    return function(y)
        return x + y
    end
end

Returning functions? That’s a little strange. Well fear not, in Lua it’s perfectly acceptable! When you call the first function, it will return another thing that you can call, just like the first. Maybe some syntax will clear it up

print(AddX(3)(4))
print(AddX(5)(6))
print(AddX(8)(9))
--[[
output:
7
11
17
]]

So here’s where things get interesting. Let’s say we store the result of AddX into some variables:

Add3 = AddX(3)
print(Add3(5))
Add4 = AddX(4)
print(Add4(5))
Add5 = AddX(5)
print(Add5(5))

Output

8
9
10

Here’s where things get interesting though. Every time you call AddX the x parameter gets overwritten right? That would be expected because there’s only one variable called “x”. However, after calling Add5 Add3(5) is still 8. This is where I define exactly what a closure is:

A closure is a variable inside a function that is defined outside the function. When the function is defined, the value of that variable is closed over, meaning that external changes to the variable are now ignored. This applies to any variable which is local. The grouping of closed over values in a particular context is called a closure. The technical definition is a record storing a function together with an environment.

This is actually pretty intuitive. You’ve probably used closures before without even realizing it. Let’s take the following spawning system:

game.Players.PlayerAdded:Connect(
    function(Player)
        Player.CharacterAdded:Connect(
            function(Character)
                 Character.Humanoid.Died:Connect(
                         function()
                              wait(5)
                              --The variable "Player" is closed over, as it is a parameter to function sent to the PlayerAdded event
                              Player:LoadCharacter()
                         end
                 )
            end
        )
        Player:LoadCharacter()
    end
)

If a closure was not created then the “Player” variable would change every time a new player joined the game, and when your character died, you would not respawn, instead the most recent player to join would respawn.

Again, you’ve probably been doing these for ages, but you might not have been able to pin down a name for them.
This is hardly the only use for closures. If you guys like this tutorial I will go into more in depth usage with them, including how they can be used to replace objects.

18 Likes

So if I had this function:

function new(new_val)
    local val = new_val
    return function(new_val2)
        val = new_val2
    end, function()
        return val
    end
end

local set, get = new(1)
print(get())
set(2)
print(get())

It would return the following?

1
1

I’m still a bit confused lol

Also that player example you gave is more just local variables, isn’t it?
The added callback function gets called with a single argument (therefore local variable) which is the Player – and you cannot externally change the value of an argument after you’ve called the function.
There is also no external Player variable.

Edit: wait, isn’t a closure just a variable that is used in a function that can be used outside of it’s scope through that function?

2 Likes

In a purely functional language you would be right, and that’s how I would implement them, however purely functional languages don’t let you change variables after they’re defined so those aren’t exactly what Lua does (I may need to fix part of my tutorial)

Closures stop consecutive calls to the same function from changing the arguments from previous times it was invoked. So an example based off of your implementation of properties is:

get1, set1 = new(1)
get5, set5 = new(5)
print(get1())
print(get5())
set5(3)
print(get1())
print(get5())

Output

1
5
1
3

Also, yes, local variables are what is closed over. The reference “Player” does not change because of consecutive function calls, so Player is closed in the subfunctions.

Similarly here is an event system implemented using a closure.

return function()
	local t = { }
	--Connect	
	return function(func)
		if (type(func) ~= "function") then
			error("Cannot connect object of type ", type(func))
			return
		end
		table.insert(t, func)
		--Disconnect
		return function()
			for i, f in pairs(t) do
				if (f == func) then
					table.remove(t, i)
					break
				end
			end
		end
	end,
	--Invoke
	function(...)
		for i, func in pairs(t) do
			func(...)
		end
	end
end

This was my first tutorial so sorry if it was hot trash.

2 Likes

I feel this post is a bit misleading in its choice of words, specifically pertaining to Lua. Lua defines all functions as being a “Closure” in the source, so that kind of overlaps what you’re trying to say. Also it’s a bit unclear but I think what you’re talking about is upvalues (locals from another stack level) as closures. What you’d be referring to is probably “closed upvalues”, which is what an upvalue in Lua is called after it exits its stack level.

2 Likes

This can possibly be connected to Python’s lambda functions that look something like this:

def Increment(num):
    return lambda x: num + x

Increment5 = Increment(5) # Increment by 5
Increment5(3)

Output:

# 8
1 Like

My favorite implementation of this is when you combine it with the ability to call functions without parentheses (in some cases). I first saw this in an instance creation function:

function create(instanceType)
   local newInstance = Instance.new(instanceType)
   return function(properties)
      --assign properties to instance
   end
end

create "Part" {
   Name = "Hello",
   Size = Vector3.new(10,10,10)
}

It surprised me back then because I never thought of returning a function, and also because I didn’t know you could call functions without the parentheses. (I believe you can only do it when your argument is a string or table and when there is only one argument)

5 Likes

Yup!

1 Like

You’re right, this is my first tutorial and my main goal was to get other people to research closures more themselves, because I find it to be the most powerful part of a lot of different languages but it’s not always talked about. Thanks for the input!

1 Like

Great tutorial.

I am still a bit confused tho

Can you provide more example code & more use cases?

Thank you.

1 Like

I found this Video, might help you understand Closure more.

2 Likes

I understand this a bit more now, but what would a practical use of this be? Can anyone give any examples?

I’m guessing that this is a (good) sample / usecase

local Success , ReturnedData = pcall(function() return RFs:InvokeClient(Player) end)

Because RemoteFunctions will Error if the Player leaves so I put it in a Pcall and then I returned the Result from the Invokation to ReturnedData, but ReturnedData will be an Error message if something goes wrong.

By doing this I didn’t have to make an Additional Variable

and if it’s not Success then you wouldn’t really need the Result from Invoking anyways.


Another Sample would be

function Closure()
 return true
end

RemoteEvent:FireServer(Closure())

From my understanding Closure is just returning a value to a function within a function

Thought this was a tutorial helping me find closure in life. :^((

7 Likes