#StandWithUkraine

Russian Aggression Must Stop


Programming fundamentals - Part 02: Values, types and expressions

2025/08/17

Tags: programming tech programming fundamentals

This is the second part in the series on covering programming fundamentals for newcomers to programming and for those refreshing their basics.

This time we'll first cover what programming is about on a high level and then proceed to play around with Lua a little bit to get

The basic principles of programming

Programming, at its most basic, is an attempt to model various processes that transform and generate data. Oftentimes our aim is to simulate or otherwise represent real-world phenomena in the form of computation that can be carried out on computers.

So, as programmers our task is to form and understanding of the key aspects of these processes and models and convert them into a form computers understand. However, this is not a one-way process and it's not merely enough for us to produce a program that is correctly understood by the computer. Software, as opposed to hardware, is malleable and most software needs to be changed over time to accommodate changing requirements. So, while we must get our point across to the dumb silicon, we must also be able to convey the function of our programs to other programmers, including the person we ourselves become after 2 weeks has passed.

Structure and Interpretation of Computer Programs lays out the tools we have at our disposal to carry out this task:

  1. We can form expressions that perform computational work.
  2. We can combine expressions together to form compound expressions.
  3. We can create abstractions for our expressions to hide unnecessary detail and to clarify out intent.

Those three tools are all we have and they are also all we need. Every major programming language supports all three, however they may differ in details in how they provide each of those tools.

Today, we will focus on the first of the three by looking at forming basic expressions in Lua.

Getting hands-on with the REPL

Lua is known as an interpreted programming language, because Lua programs typically need a program called the interpreter to execute the program code, instead of converting the program code into a binary format that the computer can directly execute.

There are certain advantages to producing so-called native executable files using a process of compilation, but an interpreter gives us different advantages, one of which is the REPL: the Read-Eval-Print Loop.

If you launch the lua program on its own, you will be presented with the following sort of view:

λ lua
Lua 5.4.6  Copyright (C) 1994-2023 Lua.org, PUC-Rio
>

This is a full-blown environment into which we can directly type our expressions and have them be interpreted and executed by Lua. It's called a REPL, because it will read an expression we type, evaluate (or execute) it, print out the result of the execution and then request another expression to be evaluated. A REPL is an immensely powerful tool that is often overlooked and we will later on explore how it can be used to incrementally and iteratively construct programs, but for now we will simply use it to explore some very basic Lua concepts.

Computation is often just a slight variation on math, so naturally we are able to create mathematical expressions and produce their output:

Lua 5.4.6  Copyright (C) 1994-2023 Lua.org, PUC-Rio
> 1 + 1
2
>

In this example, "1 + 1" is our expression and the result of that expression is printed underneath as "2".

Lua is also able to correctly follow the PEMDAS rules of operation precedence:

> 5 + 2 * 2
9

So, we essentially have ourselves a basic calculator. Useful, but not really all that powerful just yet. However, this is an important introduction to a few key things: values, types and expressions.

A value is a unit of data that we can perform computation on. A value also has an associated type, that roughly determines what operations can be performed on it. All of the values we have dealt with so far have been of type "number". We can also verify that by asking the REPL what type a value is:

> type(5)
number

You don't need to worry about what type() means just yet, but in a nutshell, it is a function provided by Lua that will take a value and produce a value indicating the type of the input.

Expressions are used to produce new values out of existing values, so that means that they also have a type:

> type(1 + 1)
number

In fact, us calling the type() function is also an expression that produces a value, and we can also look at the type of that value too!

> type(type(5))
string

And that brings us to our next data type: the string. We can produce our own strings by simply quoting text inside the REPL:

> "hello!"
hello!

You can see that the REPL printed out the value of our expression – the value itself – without the double-quotes. However, the quotes are very important in order to tell the REPL that what we wrote should be dealt with as a string. If we forget the quotes, the REPL will be hopelessly confused and print out something else:

> hello
nil

Lua doesn't know what hello is, so instead of echoing it back like it does with numbers and strings, it responded with a nil value. Nil, originating from the Latin word "nihil", means the absence of a value. Sort of counter-intuitively, nil is a value too, but it acts as marker or a representation of a lack of value. The type of nil is also nil.

In this case, because Lua doesn't know of a thing known as hello, that value doesn't exist and therefore Lua let us know this by replacing it with the nil value.

Let's go back to strings for a bit. Strings work differently from numbers in that we cannot use the same numeric operations such as addition on them:

> "hello" + "world"
stdin:1: attempt to add a 'string' with a 'string'
stack traceback:
        [C]: in metamethod 'add'
        stdin:1: in main chunk
        [C]: in ?

However, there are other functions and operators that operate on strings specifically. For example, if we want to put two strings together, meaning that we want to concatenate them, we can do so like this:

> "hello " .. "world!"
hello world!

That the .. operator does is that it copies the characters from the two strings it was given and produces a new string with the characters concatenated together. There are plenty of other functions and operators related to strings, but we don't have to worry about learning all of them right now.

An important aspect of computational logic is, well, logic. For that purpose, we have many ways to test values for equality or compare them with each other. So, let's see if 1 + 1 actually equals to 2:

> 1 + 1 == 2
true

Take that, mathematicians, that's how easy it is! We can see that the REPL responded with true, but it's important to note that this is not the same thing as a string. In fact, we can even verify that using the REPL:

> type(1 + 1 == 2)
boolean
> true == "true"
false

So, when we do comparisons between elements using the == operator, we get back a thing known as a boolean. It's just a value that is either true or false. This will be very useful when we start deciding what actions to take based on the values we have been given.

In fact, we can get a little taste of the things we'll be able to do with programmatic logic like so:

> if 1 + 1 == 2 then print("math works") else print("something is wrong") end
math works

Because of the way if expressions work, we had to use the print() function here instead of simply evaluating strings, it does exactly what you'd expect: it prints out values that have been given to it as parameters. However, it's important to understand that this is different to returning a value. In fact, print() does not return a value at all:

> type(print("hello"))
hello
stdin:1: bad argument #1 to 'type' (value expected)
stack traceback:
        [C]: in function 'type'
        stdin:1: in main chunk
        [C]: in ?

Often an expression without a return value is referred to as a "statement".

On the topic of comparisons though, it's important to note that we use two equals signs to compare values. A single equals sign does something else:

> hello = "hello world!"
> hello
hello world!

Note that when we evaluated the first expression, there was no value returned again. And when we evaluated the expression hello, this time we actually got back the string value we used in the first expression. This is known as a variable binding. Using a single = we can associate a value with a name. And unlike in math, we can update the value associated with that name after our initial binding:

> hello = "hello world!"
> hello
hello world!
> hello = "hello lua!"
> hello
hello lua!
>

In expressions, variables are fully interchangeable with direct values, so concatenation will also work with our new variable:

> hello .. " and hello world!"
hello lua! and hello world!

Note that there is also another way to create a variable binding, which is better in most situations. The method I showed you is used when dealing with "global" variables and therefore it is possible for the same variable to be set from two different places unintentionally. When you specifically want to create a new variable, you should declare it as a local variable with the local keyword:

local hello = "hello lua!"

However, in the interactive environment that would mean we wouldn't be able to access the variable. But when we are writing programs, the distinction between local and global variables becomes important.

Feel free to play with variable bindings using the other data types and values too, variables work with all of them the same way. There are also other data types that we haven't discussed yet, but we will get around to them eventually.

Writing a program

Playing with the REPL allows us to understand and explore ideas pretty well, but merely executing a series of basic expressions is a pretty tedious way to compute values, especially if we want to run the same series of computations again. So, let's actually write our first proper program!

To do this, open up your text editor and create a file in a directory of your choice and name it, for example, test.lua.

You can then copy in the following code into that file:

local secretNumber = 7

print("Guess the number!")

guess = tonumber(io.read())

print("Your guess is", guess == secretNumber)

You can now run that file with Lua:

λ lua test.lua
Guess the number!
3
Your guess is   false
λ lua test.lua
Guess the number!
7
Your guess is   true

We used a few new concepts to make this program, so let's go over it line-by-line so that we get an idea of what we actually did.

First, we create a variable binding that sets the local variable secretNumber to 7. Then we ask the user to guess the number using print(). In order for the user to actually make a guess, we need to read some user input, which we do with io.read() (io stands for input/output). However, since we want to compare this value with our secret number, we need to convert the type of the user input into a number as well, but luckily Lua allows us to do that with tonumber(). The converted value is then bound to the guess variable.

Now we can check if the user was correct with their guess. We could have used if here, but to simplify things, we simply print out the result of the comparison between the guess and our secret number. It's not the most elegant number guessing game, but it does work.

What's next?

You can see that only one guess can be made per program execution. That's because our program is linear in nature, simply executing one expression after another one without ever branching or going backwards. You can do a lot with purely linear programs, but we certainly will need to arm ourselves with some new tools to solve more complex problems.

So, next time we will look into Lua's control structures to make more complex compound expressions. We will expand on the idea of if-statements and also play with looping constructs to allow actions to be taken multiple times. See you in there!

>> Home