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,xandy, and it returns aninttype. -
To return a value from a procedure, we use the
returnkeyword.
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
echoLanguageRatingprocedure 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
argumentis now declared as avar intand not just as anint.
15
20This 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
findMaxis 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
aandb, then the result of that operation (2 + 3 = 5) is passed as the first parameter to themultiprocedure, where it is multiplied byc(5 * 4 = 20). -
First we multiply
candb, then the result of that operation (4 * 3 = 12) is passed as the first parameter to theplusprocedure, where it is added witha(12 + 2 = 14).
true
true
20
14In 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. Theresultvariable is initialized with the default value forint:0. -
When the end of the procedure is reached, the value of
resultis returned.
33Note 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 thatvarmust not be used, asresultis 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
booland 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
plusisn’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
plusprocedure to exist with this definition. -
Now we are free to use it in our code, this will work.
-
This is were
plusis 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
findMax3which 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
tickandtockwhich 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.