Kurdiez Scripting #2 : Functions, Scopes, Code Duplication, Dependency, Invariance

Functions

Functions are the modular processing powerhouse in all modern programming languages. It takes in an arbitrary number of parameters as inputs, does some processing with them and finally outputs an arbitrary number of values as results.

image

There are a number of different ways a function can be defined in Lua. For these, please research the Internet as there are tons of materials out there. Before we get into the more interesting stuff, I’ll explain what a “scope” is.

Scope

Scope is just any block of code. There can be any number of lines within a scope. Because scope is a block of code it has to have a starting point and an ending point. Different programming languages define scopes differently but in Lua a scope is created whenever you enter a new function, if-statement, loops, etc.

function someFunction()
	local text = "hello"
end

someFunction()
print(text)

When you execute this code, you will see that “nil” is printed to the output console. Although you did execute the “someFunction” and therefore created the variable text, it was created within the scope of the function. When the function was executed to completion, the text variable ceases to exist along with the function scope. How about this example?

local text = "hello"

function someFunction()
	local text = "there"
end

someFunction()
print(text)

This prints “hello” to the output console. Are you puzzled? What if we remove the keyword “local” inside the function?

local text = "hello"

function someFunction()
	text = "hello"
end

someFunction()
print(text)

We get different results just by having that local keyword or not. I’ll let you figure out what the reason is behind it. But it is critical that you understand this part so stop reading, take a break, ask questions to others if you have to and figure this out without me telling you. Once you’ve figured it out, let’s get into the more interesting stuff.

What does it mean to code better?

There is no right or wrong way to program something. If you need to achieve something and your code does exactly what you intended and produces the right results, it’s good… for the time being. However you will quickly find out that there is always a “better” way of coding within a certain context. Let’s take for example

Produce number 30 by using mathematical operations on other numbers.

There are many ways to skin this cat. You can take the integer 1 and add it 30 times in a loop. Or you can take two integers 10 and 3 and multiply them together. In terms of efficiency it’s no brainer to think that one multiplication puts less computational load on the CPU than looping 30 times to increment by 1. The point I am trying to make here is, don’t settle with the “right answer”. Always take a step back from your “correct” solution and re-think the whole approach.

Code Duplication vs. Code Dependency
Let’s define the terms first. We say Code Duplication is high when you have many lines of code that do exactly the same thing in different places. You achieve this by copy and pasting code all over the place. Code Dependency is high when you have one function that is being used by many other places. Both approaches have pros and cons. When the Code Duplication level is high, changes in one place will not affect the other places. Therefore it increases flexibility in your code. However, when your duplicated code themselves have some bug and you need to fix it, you will now have to go to 100 or 1000 different places you might have copied and pasted this block of buggy code. When the Code Dependency level is high, it is easier to fix bugs in this shared block of code because you only ever have to put in the fix in one place and it will be in effect for others that depend on it. However with dependency comes rigid-ness. The more code that depends on a function or a collection of functions, less flexible it becomes as more code is added to the codebase. Let’s take for example Roblox API. Everyone on this forum is building their game based on Roblox API. This is a strong dependency that is being formed between our code to the functions that Roblox engineers have provided to us. If ever we want to run our entire game on Unity, you’d probably end up writing the entire game from scratch. So how do you choose one way or the other?

Don’t rush to coding just because you “can” do it. Take a step back, analyze the pros and cons of both approaches within the context you are working on. Try it out. Take it as an exeprience. And move on.

Invariant Functions
Everyone who has played my demo game asks me it so cool to see different AI’s acting on their own and they would like to make something similar. This is all thanks to Object Oriented Programming which I will cover later but at the heart of it is the invariant functions. What do you call something invariant? It is not something that varies over time. It is never changing in its behaviour and therefore it produces results deterministically. Let’s look at a function that is variant first.

local health = 100

function getCurrentHealthAfterDamage(damage)
    health = health - damage
    return health
end

local output = getCurrentHealthAfterDamage(10)
print(output)    -- prints 90
output = getCurrentHealthAfterDamage(10)
print(output)    -- prints 80

Everytime we call the function “getCurrentHealthAfterDamage” with the same input 10, we get a different result. And it’s not hard to see why. This function is not considered invariant because given the same inputs, it outputs different results. Let’s write the above using an invariant function instead.

local health = 100

function getHealthAfterDamage(currentHealth, damage)
    currentHealth = currentHealth - damage
    return currentHealth 
end

health = getHealthAfterDamage(health, 10)
print(health)    -- prints 90
health = getHealthAfterDamage(health, 10)
print(health)    -- prints 80

The outputs are identical: 90 and then 80. So it’s hard to see what exactly the difference is in both approahces. However if you call “getHealthAfterDamage” many times with the same parameters (100, 10) you will always get 90 as its output. The biggest difference is that in the first function “getCurrentHealthAfterDamage”, the act of “updating the global health variable” is done right inside the function. Whereas in the second function “getHealthAfterDamage”, this is done outside of the function once the results are returned to the global scope. In other words, in the first case, the global scope is relying on the function to update its global health variable, whereas in the second case, the global scope is utilizing the invariant function to apply damage to any given health and takes on the responsibility of updating the global health variable itself. Always remember, invariant function produces the same results for the same set of inputs at any given time. Most of my code in my thousands lines of code base is made up of invariant functions and classes. With invariant functions, it is much easier to test your code function by function by giving it different inputs and seeing the outputs. Once an invariant function is confirmed correct through testing I am 100% confident that piece of code is pure and bug-free. I do encounter bugs all the time in my code. But I have much easier time making sense of the bugs and fixing them because literally 80-90% of my code is tested invariant functions that I don’t even have to look at. It’s always the code that are utilizing the invariant functions that are incorrectly using them or doing something dumb.

Conclusion
The real reason why tools and concepts like Functions, Scopes, Code Duplication, Dependency and Invariance exist is not because we can’t live without them. Technically speaking you can code any game with very old computer languages that do not have these. One of the main reasons why we have these things at our disposal is because we are always at constant battle with complexity. The complexity that lives in the logics of our code. Have you ever encountered a bug in your game and you can’t even begin where to start look into it to fix it? When above concepts are used well, you will have much easier time producing a high quality game (less bug) that is more sophisticated (usually more fun).

Contact
You can find out more about my discord and any other contact information about me on my twitch channel.

19 Likes

Pretty informative post, but regarding the invariant functions part you mentioned something about the variables being in the global scope which is a bit misleading, they’re actually still on the local scope (since globals don’t really have a scope), just at a different level (unless by global scope you meant top local scope, which is a bit confusing).

Yeah I meant the top local scope. I said that because I considered the case of single Lua script file. Also because I needed a word to describe the outer most execution context. I think it’s kind of fair to say global as far as this article is concerned.

When you explain what a scope is, I would recommend using a do ... end block for the example, as it is the simplest way to create a new scope.