Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

What are the advantages of higher ordered functions and what are they used for?

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?

1 answer

✓ Accepted answer

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!

1 comment

2

Or, if the also function scares you like it scares me, you could also just write the short function that sets both (or several) of these things.

This discussion is preserved as it was. New answers, comments, and votes are closed.