The main advantage is reuse of code, and the ability to generalize the functions you already have, without needing to rewrite them.
We'll examine a case where this might be helpful. Imagine I want to use the code above, in order to recolor a bunch of parts in my place, based on what their name is. (Maybe I generated them, or I decided that they'd all be better in a new color).
We'll see that the advantage of higher-ordered functions isn't where you call those functions- it's being able to reuse new ones where you used the old ones.
local parts = game.Workspace.Model:GetChildren()
function makeColorChanger( str )
return function(part)
part.BrickColor = BrickColor.new(str)
end
end
local transforms = {}
transforms.Wall = makeColorChanger("Medium stone grey")
transforms.Wood = makeColorChanger("Dark orange")
transforms.Door = makeColorChanger("Bright blue")
for i = 1, #parts do
local part = parts[i]
local name = part.Name
if transforms[name] ~= nil then
transforms[name]( part )
end
end
Okay, that's fine. Now, let's try it using a table of colors, rather than functions:
local parts = game.Workspace.Model:GetChildren()
local colors = {}
colors.Wall = "Medium stone grey"
colors.Wood = "Dark orange"
colors.Door = "Bright blue"
for i = 1, #parts do
local part = parts[i]
local name = part.Name
if colors[name] ~= nil then
part.BrickColor = BrickColor.new( colors[name] )
end
end
Of the two, the second is actually probably simpler and better. But this isn't where higher order functions shine!
So, the above works okay... until you realize that you want to change Reflectance too! There happens to be a couple parts named "Mirror" that you want to update too, to change their Reflectance to 1. We also have some "Gold" that's supposed to have reflectance 0.5, and "Iron" which has 0.2.
So, let's look at how you'd do it the second way:
local parts = game.Workspace.Model:GetChildren()
local colors = {}
colors.Wall = "Medium stone grey"
colors.Wood = "Dark orange"
colors.Door = "Bright blue"
local refls = {}
refls.Mirror = 1
refls.Gold = 0.5
refls.Iron = 0.2
for i = 1, #parts do
local part = parts[i]
local name = part.Name
if colors[name] ~= nil then
part.BrickColor = BrickColor.new( colors[name] )
end
if refls[name] ~= nil then
part.Reflectance = refls[name]
end
end
Great! It's working again.
Oops, I forgot I want to change material too. Signs needs to be actual "Wood", and "Rocks" are slate. Road will be "Pebble"
Okay, let's add that too:
local parts = game.Workspace.Model:GetChildren()
local colors = {}
colors.Wall = "Medium stone grey"
colors.Wood = "Dark orange"
colors.Door = "Bright blue"
local refls = {}
refls.Mirror = 1
refls.Gold = 0.5
refls.Iron = 0.2
local materials = {}
materials.Sign = "Wood"
materials.Rock = "Slate"
materials.Road = "Pebble"
for i = 1, #parts do
local part = parts[i]
local name = part.Name
if colors[name] ~= nil then
part.BrickColor = BrickColor.new( colors[name] )
end
if refls[name] ~= nil then
part.Reflectance = refls[name]
end
if materials[name] ~= nil then
part.Material = materials[name]
end
end
Okay, whew. Except, wait, I need to change transparency too!
What you're finding is that you have to add new code everywhere in order to support small changes. We can do this in a much simpler way using higher-order functions.
Look, we don't have to change our loop at all!
local parts = game.Workspace.Model:GetChildren()
function makeColorChanger( str )
return function(part)
part.BrickColor = BrickColor.new(str)
end
end
function makeReflectanceChanger( refl )
return function(part)
part.Reflectance = refl
end
end
function makeMaterialChanger( mat )
return function(part)
part.Material = mat
end
end
local transforms = {}
transforms.Wall = makeColorChanger("Medium stone grey")
transforms.Wood = makeColorChanger("Dark orange")
transforms.Door = makeColorChanger("Bright blue")
transforms.Mirror = makeReflectanceChanger(1)
transforms.Gold = makeReflectanceChanger(0.5)
transforms.Iron = makeReflectanceChanger(0.2)
transforms.Sign = makeMaterialChanger("Wood")
transforms.Rock = makeMaterialChanger("Slate")
transforms.Road = makeMaterialChanger("Pebble")
for i = 1, #parts do
local part = parts[i]
local name = part.Name
if transforms[name] ~= nil then
transforms[name]( part )
end
end
This is obvious a very simple example. However, you can imagine that we have more than one of these loops. Not one place to add in these new properties, but several, or even dozens. Higher order functions help make this much, much easier to work with.
Naturally, there are some costs. They can be more difficult to debug and reason about (but they also make things much simpler when used properly). There's also a significant performance penalty, especially in Lua, for using them. You won't notice this unless you are constantly creating new functions, and only in code that runs extremely slowly because it does a lot of computation, but it's still a factor you sometimes need to take into consideration.
One problem you might have noticed is that I avoided setting multiple properties for the same name. There's a reason for this: without the HOFs we have it easier, since we can update them separately. With them, we can't do that (or so it seems!). We can fix this, however. Here's one way.
function also(...)
local funs = {...}
return function(part)
for i = 1, #funs do
funs[i](part)
end
end
end
transforms.Gold = also( makeColorChanger("Bright yellow"), makeReflectanceChanger(0.5) )
We can go further in using HOFs. For example, suppose I have a table of parts, and I want to apply the same transformation to them. I can make a new function:
function applyToAll(fun, table)
for i = 1, #table do
fun(table[i])
end
end
Now if we have a list of parts call goldCoins, and a list of gold jewelry called jewelry and a list of gold furniture parts called goldFurniture, then I can make them all gold by doing the following:
local goldTransformation = also( makeColorChanger("Bright yellow"), makeReflectanceChanger(0.5) )
applyToAll( goldTransformation, goldCoins );
applyToAll( goldTransformation, jewelry);
applyToAll( goldTransformation, goldFurniture );
Now, if I want to change what the transformation does, like using a reflectance of 0.4 instead of 0.5, I only have to change one number. I don't even have to look at any loops here!
1 comment
Or, if the
alsofunction scares you like it scares me, you could also just write the short function that sets both (or several) of these things.