Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
6

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

Asked by
Perci1 4988 Trusted Moderation Voter Community Moderator
9 years ago

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

Log in to vote
8
Answered by
TaslemGuy 211 Moderation Voter
9 years ago

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!

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. BlueTaslem 18071 — 9y
Ad

Answer this question