Part 8 — A complete beginner’s guide to Computer Programming with Clojure: Functions

Photo by Roman Mager on Unsplash

A function is just a piece of code that performs a specific task and can be ‘called’ when required. You have seen functions used throughout this course. For instance, mathematical + and println are examples of inbuilt functions. Often, but not always, a function expects an argument or arguments.

For example,

+ is the function for addition.

5 and 10 are the arguments.

This function will work with 1 or more parameters.

Recall from Part 6, a parameter is like a placeholder where the function expects to receive an argument.

Arity

An empty function, known as 0 (zero) arity because it doesn’t take any arguments:

A 1-arity function that has one parameter x that takes only one argument:

Two or more arity is known as a Multi-arity function. In other words, you specify how many parameters you have to take arguments: So, a 2-arity function has 2 parameters that take 2 arguments:

Here, the function has actually done this: 2 multiplied by 2 to give 4, then multiplied 4 by 3 to give 12.

However, if we write the function like this:

This time, the function just displayed the result of the last calculation e.g. 3 multiplied by 2.

We can check this by reversing the numbers.

Therefore, if we wish to take two numbers and display the double of both together side-by-side, we will need to write our function like so:

Variadic Functions

A Variadic function is any function that can accept a number of arguments but you don’t know exactly how many, or it varies.

Have a look at the following:

This function has two extra parts, & and apply. The word more is just a parameter like y. So the above function would be the same as:

In this function, the & implies any number of arguments may follow, and these will be a list. The keyword apply is placed before the function to instruct it to apply it. In this case, it applies multiplication against each argument collected together as a list.

Note * , is the symbol for multiplication but it is also a function similar to our function called mult.

Consider the following:

This brings us to another point, a function can call another function.

We will make another function called multy that will call our function mult instead of *.

Earlier, I stated apply applies multiplication to a list.

Consider the following:

The multiplication will not apply to a list. The same error will occur for other mathematical operators.

But if we add in our keyword apply

This is important to understand as our functions will often work with data, often as a list or a vector like so:

All apply does is change this

to this

It does the same with text

or (to a vector)

In other words, apply changes the above to

There is another built-in function (key-word) that works differently to apply, but produces the same result (usually).

The key-word is reduce

Instead of a straight forward translation like apply, reduce process the list like so:

1 * 2 = 2

2 * 3 = 6

6 * 4 = 24

24 * 5 = 120

120 * 6 = 720

This is the equivalent of

It also works the same way with strings

which is interpreted thus:

“Star” + “ Trek” = “Star Trek”

“Star Trek” + “ — the Wrath of Khan” = “Star Trek — the Wrath of Khan”

So which one should you use?

There is much online debate about this. It is claimed that reduce is processed faster, especially against very large lists or vectors. In addition, they don’t always produce the same result. For example:

translates to this

to produce

Whereas

produces

So, always check to see whether reduce works the way you expect it to.

Function example

Let’s revisit our Library database. This time we will improve on our database. A hash-map works upon key values. We will operate our Library database like so:

{:TITLE {Value {:AUTHOR Value,

:YEAR Value,

: ISBN} Value,

: NOTES Value }

}

}

For Example:

Therefore, to insert anything into the Library database we will specify a Title and another known value e.g. Author. If, for example, the author is unknown a value can be created e.g. “Anonymous” or “Unknown”. This way each Title value is paired with an author-value, even if the author is unknown.

First, we will define some variables, otherwise known as Atoms, to hold our data. The Library database will be a variable defined as an empty hash-map:

Note, empty brackets { } is simply a way of declaring an empty hash-map.

For Title, Author, Isbn, Year and Notes we will declare them as empty variables

Now we will create a function to take in two arguments. Remember, arguments are what you provide to parameters. Our first function will take the Book Title and the Author. Have a look at the following:

Here, x takes in the Book Title and y takes the Authors name. The values held by the atoms Title and Author are changed by the reset! function to ensure our variable will hold the new values.

Now, let’s add another line into our function to push these new variable values into our Library database (hash-map). Consider the following:

This works fine. Nevertheless, we will separate the last part into a separate function which we will then call. Breaking functions up into smaller functions makes the code easier to read and prevents the creation of huge, difficult to understand, monolith functions. We will create a new function just to insert our data into our Library:

Now, to complete the task, our InsertAuth function calls the AuthFn function.

Decision making

Functions can make decisions. Consider the following:

This function uses a keyword if to ask a question by calling another keyword number? If the answer is true it will immediately go to the next section ( println “true” ) and the function will stop there i.e. it will not go to the next section. However, if the answer is not true, it will skip the first section and, instead, process the last section ( println “false” ).

The general format is as follows

( if ( Question Answer )

(if the Answer is True execute this line only)

(if the Answer is False execute this line only) )

Therefore:

Let’s write a function that will only accept a number for the Year a book was published.

For clarity, let’s break down the relevant sections:

1. (if (number? @Year ) Look at what is stored in the Year variable and see if it is a number

2. ( swap! Library assoc-in [:TITLE @Title :YEAR ] @Year) If it is a number, then add it to the Library database

3. (println “Please input a number without quotes.\n\n” (reset! Year nil) If no number is stored in the variable, then a message will be printed out and the Year variable will be reset to nil.

To summarise, If number 1. is true then 2. will be executed. However, if number 1. is false, then number 3. will be executed.

You may be wondering about the \n at the end of the println statement. This is an example of formatting. The \n is not printed but is an instruction to print a new line. Hence two \n\n will print two new lines.

The \ is called an escape character. It escapes the character n so that n becomes a command for new line. Escape characters are used frequently. For example:

\t tab

\f formfeed

\b backspace

\r return

The escape character can also be used to display special characters by their octal number e.g:

The ISBN number consists of numbers, hyphens (-), and upper or lower case X (x). We will introduce the Regular Expression. A Regular Expression is often referred to as regex. Regex can be described as a set of characters that describe a searchable pattern. Regular Expressions will be looked at again in Part 9. For now, consider the following regex:

The # specifies that a regex pattern will follow ^ specifies the start of the line. 0–9 is any number from 0 to 9. The letters x and X. The minus is negated \ to produce a literal (hyphen). The + means one or more instances and $ is the end of the line. So, reading from left to right, match a string that has any positive integer from 0 to 9; and/or an x; and/or X; and/or -. The following would match:

“89789789–7878–8989”

“56656768676”

“67548–7878–898X”

Whereas, “67548–7878–898Y” would fail.

With this in mind, let’s write a function to only allow correctly formatted ISBN numbers into the Library database.

Our ISBN function (ISBNFn) uses the key-word re-find to find the matching regex pattern inside the variable @Isbn. If it matches our pre-defined pattern, it will continue to the next section and update the Library database. Else, it jumps to the last section and prints a warning, and resets the Isbn variable.

Next, let’s work on the Notes function. It would be useful to restrict the number of characters inside Notes — somebody has to read them!

Before we tackle the Notes function, consider the count key-word:

Clearly, count counts the number of characters (including spaces) in between the quotes and returns a number.

The following details a string of characters held in the Notes variable:

If we apply the count function to @Notes:

We see @Notes holds a string of characters totaling 115.

Consider the following:

This evaluates as: if 115 is < (less than) 140 then True, else it is False.

Here is our Notes function. This function will only accept notes that contain less than 140 characters.

Our Year function currently only accepts a number. Unfortunately, it can accept any number. We can improve the function with a regular expression. This time, allow it to accept a string but the string must be a number in the year format.

Consider the following:

This regex translates to:

^[12] — The first character is either a 1 or a 2.

[0–9]{3}$ — The remaining characters can be any number but must be 3 digits in length.

Therefore, 1927 and 2020 are acceptable. Whereas, 3020 would not.

Here is the new Year function:

Let’s recap with what we have done so far to improve our Library database. First, we created an empty database consisting of true variables in the form of Atoms. We now know that an Atom is an essential part of the Functional programming paradigm and allows for much safer code execution as all threads or subprocesses will be executed without issue.

Specifically, the Atoms created are: Library, Title, Author, ISBN, Notes, and Year.

Next, we created 4 functions for inserting data into our Library database. Here, we introduced decision making and controls to help ensure the data inserted was correct or in a specific format. All 4 functions are multi-arity functions, that also call another function. Our insert functions are: InsertAuth, InsertYear, InsertISBN, InsertNotes.

To finish off, I have created both Find and Delete functions. They are all 1-arity functions. At this juncture, these functions should not require any explanation.

Find Functions

Delete Functions

Saving the database as a file

The problem with creating Code in a REPL is you will lose everything as soon as you exit the REPL. We will talk about files in a later post. For the moment, we will look to see how we can save our database as a file.

First, we will need to import some pre-written functionality into our REPL session. This will allow us to both save the database and import it back into a new REPL session. Type in the following.

My current Library looks like so

Now I will save the Library (into the same directory the REPL is running). I will call it Library.txt.

I will then exit out of the REPL and start a completely new REPL session.

First, set up the necessary functionality.

You will need to recreate an empty Atom with the same Library name.

Now, just fill it with the data from the Library.txt file.

Just to check, I will use pprint (pretty-print) to display an easy-to-read version.

However, you will need to retype the functions!

SUMMARY

In this post, we extended our knowledge of Maps and Atoms and even touched upon Regular Expressions (REGEX) and files. Nevertheless, the primary purpose here was to show you how to create your own functions. In addition, it is important to recognize that other programming languages (and programmers) may refer to a function as a Method or a Subroutine.

There is a lot of jargon and concepts written about functions. For instance, when a function is given a name (like we did with def) it is considered to be first-class. If the function does not have a name then it is an anonymous function. As it has no name, an anonymous function cannot be ‘called’ so tends to be used inside another function. A function can also be considered overloaded if it can be applied to different situations. For example, a function could be called Area and could calculate either the area of a square, triangle, or circle. It is considered overloaded as it is a function of functions under one name. All you need to do is find a way of ‘informing ’ the function of where it is being applied e.g. square, triangle, or circle.

Previous

Part 9 — REGEX