In this question, BlueTaslem stated that higher ordered functions are awesome, and proceeded to give this example;
function makeColorChanger( str ) return function(part) part.BrickColor = BrickColor.new(str) end end makeRed = makeColorChanger("Bright red") makeBlue = makeColorChange("Bright blue") makeGreen = makeColorChange("Bright green") -- etc! -- Let's make the baseplate red: makeRed( workspace.BasePlate ) -- OR: makeColorChanger("Bright red")(workspace.BasePlate) -- Funny looking, but awesome that it works
But when would this really be used? Alright interesting, this example could be simplified to;
function changeColor(color, part) part.BrickColor = BrickColor.new(color) end changeColor("Bright red", workspace.BasePlate) --Or any other color.
When would higher ordered functions really be necessary? Why would you return a function when you can just add an extra parameter?
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.
-- COLORS: HIGHER ORDER FUNCTIONS local parts = game.Workspace.Model:GetChildren() -- pretend this is an actual list of parts function makeColorChanger( str ) return function(part) part.BrickColor = BrickColor.new(str) end end local transforms = {} -- will tell me how to change a part 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 -- if I know about that name, run that function on the part transforms[name]( part ) end end
Okay, that's fine. Now, let's try it using a table of colors, rather than functions:
-- COLORS: STRINGS local parts = game.Workspace.Model:GetChildren() -- pretend this is an actual list of parts local colors = {} -- will tell me what color to change a part 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 -- if I know about that name, change the brick's color 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() -- pretend this is an actual list of parts local colors = {} -- will tell me what color to change a part colors.Wall = "Medium stone grey" colors.Wood = "Dark orange" colors.Door = "Bright blue" local refls = {} -- will tell me what reflectance to make a part 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 -- if I know about that name, change the brick's color part.BrickColor = BrickColor.new( colors[name] ) end if refls[name] ~= nil then -- if I know about that name, change the brick's reflectance 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() -- pretend this is an actual list of parts local colors = {} -- will tell me what color to change a part colors.Wall = "Medium stone grey" colors.Wood = "Dark orange" colors.Door = "Bright blue" local refls = {} -- will tell me what reflectance to make a part refls.Mirror = 1 refls.Gold = 0.5 refls.Iron = 0.2 local materials = {} -- will tell me what material to make a part 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 -- if I know about that name, change the brick's color part.BrickColor = BrickColor.new( colors[name] ) end if refls[name] ~= nil then -- if I know about that name, change the brick's reflectance part.Reflectance = refls[name] end if materials[name] ~= nil then -- if I know about that name, change the brick's material 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!
-- COLORS: HIGHER ORDER FUNCTIONS local parts = game.Workspace.Model:GetChildren() -- pretend this is an actual list of parts 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 = {} -- will tell me how to change a part 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 -- if I know about that name, run that function on the part 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!