Procedures, or functions as they are called in some other programming languages, are parts of code that perform a specific task, packaged as a unit. The benefit of grouping code together like this is that we can call these procedures instead of writing all the code over again when we wish to use the procedure’s code.
In some of the previous chapters we’ve looked at the Collatz conjecture in various different scenarios. By wrapping up the Collatz conjecture logic into a procedure we could have called the same code for all the exercises.
So far we have used many built-in procedures, such as echo
for printing, add
for adding elements to a sequence, inc
to increase the value of an integer, len
to get the length of a container, etc.
Now we’ll see how to create and use our own procedures.
Some of the advantages of using procedures are:
-
Reducing code duplication
-
Easier to read code as we can name pieces by what they do
-
Decomposing a complex task into simpler steps
As mentioned in the beginning of this section, procedures are often called functions in other languages.
This is actually a bit of a misnomer if we consider the mathematical definition of a function.
Mathematical functions take a set of arguments (like f(x, y)
, where f
is a function, and x
and y
are its arguments) and always return the same answer for the same input.
Programmatic procedures on the other hand don’t always return the same output for a given input.
Sometimes they don’t return anything at all.
This is because our computer programs can store state in the variables we mentioned earlier which procedures can read and change.
In Nim, the word func
is currently reserved to be used as the more mathematically correct kind of function, forcing no side-effects.
Before we can use (call) our procedure, we need to create it and define what it does.
A procedure is declared by using the proc
keyword and the procedure name, followed by the input parameters and their type inside of parentheses, and the last part is a colon and the type of the value returned from a procedure, like this:
proc <name>(<p1>: <type1>, <p2>: <type2>, ...): <returnType>
The body of a procedure is written in the indented block following the declaration appended with a =
sign.
link:{source-dir}/callProcs.nim[role=include]
-
Declaring procedure called
findMax
, which has two parameters,x
andy
, and it returns anint
type. -
To return a value from a procedure, we use the
return
keyword.
proc echoLanguageRating(language: string) = (1)
case language
of "Nim", "nim", "NIM":
echo language, " is the best language!"
else:
echo language, " might be a second-best language."
-
The
echoLanguageRating
procedure just echoes the given name, it doesn’t return anything, so the return type is not declared.
Normally we’re not allowed to change any of the parameters we are given. Doing something like this will throw an error:
proc changeArgument(argument: int) =
argument += 5
var ourVariable = 10
changeArgument(ourVariable)
In order for this to work we need to allow Nim, and the programmer using our procedure, to change the argument by declaring it as a variable:
proc changeArgument(argument: var int) = (1)
argument += 5
var ourVariable = 10
changeArgument(ourVariable)
echo ourVariable
changeArgument(ourVariable)
echo ourVariable
-
Notice how
argument
is now declared as avar int
and not just as anint
.
15
20
This of course means that the name we pass it must be declared as a variable as well, passing in something assigned with const
or let
will throw an error.
While it is good practice to pass things as arguments it is also possible to use names declared outside the procedure, both variables and constants:
var x = 100
proc echoX() =
echo x (1)
x += 1 (2)
echoX()
echoX()
-
Here we access the outside variable
x
. -
We can also update its value, since it’s declared as a variable.
100 101
After we have declared a procedure, we can call it. The usual way of calling procedures/functions in many programming languages is to state its name and provide the arguments in the parentheses, like this:
<procName>(<arg1>, <arg2>, ...)
The result from calling a procedure can be stored in a variable.
If we want to call our findMax
procedure from the above example, and save the return value in a variable we can do that with:
link:{source-dir}/callProcs.nim[role=include]
-
The result from the function
findMax
is here namedc
, and is called with the results of our first two calls (findMax(987, 321)
).
987
321
987
Nim, unlike many other languages, also supports Uniform Function Call Syntax, which allows many different ways of calling procedures.
This one is a call where the first argument is written before the function name, and the rest of the parameters are stated in parentheses:
<arg1>.<procName>(<arg2>, ...)
We have used this syntax when we were adding elements to an existing sequence (<seq>.add(<element>)
), as this makes it more readable and expresses our intent more clearly than writing add(<seq>, <element>)
.
We can also omit the parentheses around the arguments:
<procName> <arg1>, <arg2>, ...
We’ve seen this style being used when we call the echo
procedure, and when calling the len
procedure without any arguments.
These two can also be combined like this, but this syntax however is not seen very often:
<arg1>.<procName> <arg2>, <arg3>, ...
The uniform call syntax allows for more readable chaining of multiple procedures:
link:{source-dir}/ufcs.nim[role=include]
-
If multiple parameters are of the same type, we can declare their type in this compact way.
-
First we add
a
andb
, then the result of that operation (2 + 3 = 5) is passed as the first parameter to themulti
procedure, where it is multiplied byc
(5 * 4 = 20). -
First we multiply
c
andb
, then the result of that operation (4 * 3 = 12) is passed as the first parameter to theplus
procedure, where it is added witha
(12 + 2 = 14).
true
true
20
14
In Nim, every procedure that returns a value has an implicitly declared and initialized (with a default value) result
variable.
The procedure will return the value of this result
variable when it reaches the end of its indented block, even with no return
statement.
link:{source-dir}/result.nim[role=include]
-
The return type is
int
. Theresult
variable is initialized with the default value forint
:0
. -
When the end of the procedure is reached, the value of
result
is returned.
33
Note that this procedure is here to demonstrate the result
variable, and it is not 100% correct:
if you would pass a sequence containing only negative numbers, this procedure would return 0
(which is not contained in the sequence).
Warning
|
Beware!
In older Nim versions (before Nim 0.19.0), the default value of strings and sequences was nil , and when we would use them as returning types, the result variable would need to be initialized as an empty string ("" ) or as an empty sequence (@[] ).
|
link:{source-dir}/result.nim[role=include]
-
In Nim version 0.19.0 and newer, this line is not needed — sequences are automatically initialized as empty sequences.
In older Nim versions, sequences must be initialized, and without this line the compiler would throw an error. (Notice thatvar
must not be used, asresult
is already implicitly declared.)
@[1, 43, 57]
Inside of a procedure we can also call other procedures.
link:{source-dir}/filterOdds.nim[role=include]
-
Once again, this line is not needed in the newer versions of Nim.
-
Calling the previously declared procedure. Its return type is
bool
and can be used in the if-statement. -
The third way of calling a procedure, as we saw above.
@[6, 9, 0, 3]
@[3]
@[45390, 3219]
As mentioned in the very beginning of this section we can declare a procedure without a code block. The reason for this is that we have to declare procedures before we can call them, doing this will not work:
echo 5.plus(10) # error (1)
proc plus(x, y: int): int = (2)
return x + y
-
This will throw an error as
plus
isn’t defined yet. -
Here we define
plus
, but since it’s after we use it Nim doesn’t know about it yet.
The way to get around this is what’s called a forward declaration:
proc plus(x, y: int): int (1)
echo 5.plus(10) (2)
proc plus(x, y: int): int = (3)
return x + y
-
Here we tell Nim that it should consider the
plus
procedure to exist with this definition. -
Now we are free to use it in our code, this will work.
-
This is were
plus
is actually implemented, this must of course match our previous definition.
-
Create a procedure which will greet a person (print "Hello <name>") based on the provided name. Create a sequence of names. Greet each person using the created procedure.
-
Create a procedure
findMax3
which will return the largest of three values. -
Points in 2D plane can be represented as
tuple[x, y: float]
. Write a procedure which will receive two points and return a new point which is a sum of those two points (add x’s and y’s separately). -
Create two procedures
tick
andtock
which echo out the words "tick" and "tock". Have a global variable to keep track of how many times they have run, and run one from the other until the counter reaches 20. The expected output is to get lines with "tick" and "tock" alternating 20 times. (Hint: use forward declarations.)
Note
|
You can press Ctrl+C to stop execution of a program if you enter an infinite loop. |
Test all procedures by calling them with different parameters.