It’s time for part 5 in our programming for beginners series.
For links to the previous editions, check out
Functions Introduction
You can look at functions as small machines.
Functions are something that we’ve included in most, if not all of our previous episodes without talking much about them. Depending on language and/or usage they might be called Methods instead, but we’ll call them functions throughout this post.
You can look at functions as small machines. Sometimes they need some raw material (inputs) and sometimes they give a product in return (output). And sometimes they launch a rocket or blink a LED. Functions can have either inputs, output, both or neither. They can have as many inputs as you want, but typically not more than one output.
For those of you who are versed in mathematics: these functions are very relatable to mathematical functions. For instance:
where x and y are the inputs and the result of the equation, after setting the inputs to some values, is the output.
Sometimes you write your own functions, but just as often (depending on the application, of course) you use pre-made functions which is either built into the language or is part of some API or similar.
Calling Functions
To make functions less abstract we can say that they are a chunk of code which you can run wherever/whenever you want and as many times as you want. The correct term for this activation is to call a function. When you call a function the program will basically run the chunk of code inside the function before continuing on as before. Further down we’ll show an example where we call functions we’ve made ourselves.
Inputs aka. Parameters
As previously mentioned, functions can have any number of inputs. These inputs are called parameters and usually come in the form of variables. Other, more intricate datatypes can also be set as parameters, but we’ll keep it simple for now.
Output aka. Return Type
We also mentioned that functions can only have one output. We usually say that the function return something instead of saying that it outputs something. Functions are a bit more strict when it comes to what datatypes they return compared to what they can take as parameters. We’re not going to go into detail around this subject, but for instance if you’re trying to return an array in Arduino/C++ as you would return an integer, you’re gonna have a bad time.
Creating a Function
We’re now going to go through the different parts of a function as well as show some examples of (somewhat useless) functions written from scratch.
The Definition
The meat of the function, the actual code that defines what the function does, is called the definition. This consists of a function header and a function body.
Function Header
The function header consists of return type (not necessary in Python), function name and a list of parameters (in that order).
Function Body
This part of the definition is where the code that actually defines what the function does is written.
Two Arduino/C++ Examples
Example time!
int my_sum_function(int a, int b){ int my_sum = a + b; return my_sum; }
In this example we’ve made a simple function that takes two integers and returns the sum of those two.
Line 1 is the function header.
int
is the return type. It must coincide with what you are trying to return in the function body.my_sum_function
is the function name. This is used when calling the function.int a
andint b
are parameters. If we try to feed the function other datatypes or a different number of parameters, we will get a compilation error.
For clarity we’ve written the function body over two lines where it easily could’ve been written on one single line like this: return a + b;
.
Let’s look at a different example:
void print_something(void){ Serial.println("something"); }
Here’s another example with a function that returns nothing and take no parameters. It just print the text something over serial. Notice that void
replaces the return type and the parameter list. Also, we don’t use return
in the function body when there’s nothing to return.
If you write the function yourself you should place the definition below your main code or in a seperate file. This is for languages that uses prototypes (which we will look at in the next chapter). Python does not.
Two Python Examples
def my_sum_function(a, b): my_sum = a + b return my_sum
Here we have the Python equivalent to the first example above. Notice the def
instead of the return type. All function definitions in Python start with def
.
def print_something(): print "something" return
Here’s the equivalent to the second example. We still use def
, but keep the parameter list empty. We also keep the return
statement, however with nothing behind it. This way it will exit the function without returning anything.
Since Python doesn’t use prototypes you need to place your function definitions above your main code so that the program is aware of the function before it’s called.
The Prototype
The function prototype is not a thing in all languages (not in Python, for instance). Many languages use them, though. Prototypes are very similar to the function header in the function definition.
When writing a function yourself you should place the prototype above the main code or in a header file. This is becuase the compiler needs to be aware that a function’s name, parameter list and return type before it’s called.
Arduino/C++ Example
Let’s create prototypes for the two examples above.
int my_sum_function(int a, int b);
and
void print_something(void);
As you can see, these are near identical to the function headers in the definition.
Complete Examples
Let’s use what we’ve learned so far in one single example.
Arduino/C++
int my_sum_function(int a, int b); void print_something(void); void setup(){ Serial.begin(115200); int number_one = 3; int number_two = 2; int number_three = my_sum_function(number_one, number_two); } void loop(){ print_something(); delay(1000); } int my_sum_function(int a, int b){ int my_sum = a + b; return my_sum; } void print_something(void){ Serial.println("something"); }
Here we use the two almost completely useless functions created earlier. The first two lines are prototypes for both functions. In the setup()
chunk the code runs just once. Here we set the serial baud rate before declaring and initializing two integer variables.
Then, on line 10 we call the function my_sum_function()
which takes two parameters, namely the two previously declared integers. At the same time we declare a third integer which we initialize with the return value of the function.
Later, in the loop()
function, we call the function print_something()
once each second. Notice how we include the empty parentheses since the function doesn’t take input parameters.
We also call a couple of built-in functions specific to the Arduino language here, namely the Serial.begin()
, delay()
and Serial.println()
functions.
Python
import time def my_sum_function(a, b): my_sum = a + b return my_sum def print_something(): print "something" return number_one = 3 number_two = 2 number_three = my_sum_function(number_one, number_two) while True: print_something() time.sleep(1)
This is the equivalent Python code for the functionality in the Arduino example above. Notice how we don’t use prototypes, but instead define the functions on top before calling them.
while True:
is equivalent to Arudino’s loop()
function.
import time
is needed to gain acces to the built-in function time.sleep()
. The input parameter is the number of seconds you want the program to wait as opposed to milliseconds in Arduino’s delay()
function.
Why and When to Use Functions?
Sometimes you want to do a certain task, simple or not, multiple times during a program, but a for loop is far from the solution. Maybe you’re making an embedded system and want to open a door at a certain temperature, when the time is 3pm or when you push a button. Then you typically write a function that opens the door and then call it when the required condition(s) are met which can be several different places in the code. This way you don’t have to clutter the code with copypasta all over the place – writing the code once is enough.
However, regardless of how many times you want to call a function you often want move a chunk of code that works towards a common goal into a function (for instance the code that opens the door). This tidies up the code into modules and makes it much easier to do debugging and general development as well as increases the readability..
It is also very common to call functions within other functions. This way you can for instance write high-level functions which call mid-level functions which again call low level functions that deal with HW, a very orderly way to split your code into levels. Start at the bottom and build your way up, or (less recommended) the other way around.
Scope
The code may or may not know about a variable depending on where the it was declared.
One important topic related (but not limited) to functions is scope. Scope is basically a term for accessability. The code may or may not know about a variable depending on where the it was declared.
If you declare a variable inside a function, we say that the variable is local. This variable will not be accessible outside of the function. It will also normally be erased from the memory when the function is finished running.
On the other hand, you can declare global variables. These are accessible anywhere in the code and remains in the memory throughout the program runtime. Where you declare global variables is a question of coding etiquette and varies from language to language, but usually they are declared near the top of the code.
There are often many levels of scopes in a piece of code. The rule of thumb is that a variable declared in a certain scope is not accessible in a higher level scope.
Scopes in Arduino/C++
While coding on an arduino, scopes are recognized by curly brackets { }
. These curly brackets are not only found in functions, but also in things like if statements and loops where they also represent scopes.
The functions setup()
and loop()
are great examples to show where variables are accessible.
int my_global_var = 2; void setup(){ int my_first_local_var = 4; if(my_first_local_var == 4){ int my_second_local_var = 8; } } void loop(){ int my_third_local_var = 6; }
my_global_var
is accessible everywhere in the code and will not be deleted before the program is shut down.my_first_local_var
is accessible everywhere within thesetup()
function (including inside the if statement) and will be deleted after it is finished.my_second_local_var
is only accessible within the if statement and will be deleted after it is finished.my_third_local_var
is accessible within theloop()
function and will remain as long the function is running.
Scopes in Python
The indents i Python are the equivalent to the curly brackets in Arduino/C++. These indents are crucial to get your program to work (as opposed to the indents in Arduino/C++) and they define the scopes in your program. The same rules as in Arduino/C++ apply when it comes to variable accessability and longevity.
When to Use Global Variables
As a rule of thumb you should use global variables only when you absolutely need to.
It might be tempting to just make all variables in your program global such that you don’t need to worry about scope at all. This is however not recommended. As a rule of thumb you should use global variables only when you absolutely need to. Keep your variables as local as possible (to a certain degree, of course). They will be tossed away when they aren’t needed anymore. This makes your code tidier and you won’t hog up more memory than necessary.
You also have a security and bug aspect to think about when your programs become large where you want to have more control over your variables and keep them from being altered when you don’t want them to (for instance by other coders working on your software).
Summary
This ended up being a longer post than first estimated. This is probably because functions actually is a quite complex topic, but at the same time very easy to use.
Key points:
- Functions have up to multiple inputs.
- They have only up to one output.
- Place prototype before main code if able.
- If not, place definition before main code.
- It’s better to use functions too often than too rarely.
- Variables declared within a scope are not accessible outside that particular scope.
- Try to declare as few as global variables as possible. Make them local instead.
Scope is a very important concept to understand, mostly because of variable accessability. Think twice when declaring variables. Does it really need to be global? Will the program need to access it in a different scope?
This was part 5 in our programming for beginners series. Stay tuned for part 6 in the near future!