DEV Community

Cover image for Learning Clojure, part V
Камило Куньа
Камило Куньа

Posted on • Edited on

Learning Clojure, part V

In the previous part we learnt what are forms, how Clojure evaluates them into values and how to return code as data. Now we gonna learn about a fundamental underlying data structure behind all of it: the list.

The meaning of being LISP

The name LISP derives from "LISt Processor" and the reason for it is because everything in the language is in fact a list. What we call as a "list" is in fact the Linked List data structure that is represented in Clojure and all other historically LISPs as values between parenthesis separated by a space.

("This" "is" "a" "list")
Enter fullscreen mode Exit fullscreen mode

As we can see the forms that compose the language syntax are a linked list with a function as the first element that is applied in all the remaining elements from the list.

The image shows the first element of the list being applied to the remaining ones in the list. Also show the linked list of a form with each element as a value and a pointer to the position of the next

Image: "Forms as linked lists" made by the author is copyleft material under unlicense.

(str "This " "is" " a " "list")
;; returns => "This is a list"

(apply str (list "This " "is" " a " "list"))
;; returns => "This is a list"
Enter fullscreen mode Exit fullscreen mode

This linked list is formed with pairs of elements e.g. (1 (2 (3 ()))) that usually are called cons. In most LISPs implementations cons are a structure of a pair of elements (A.B) in Clojure the underlying structure are Seqs that we'll see later, but the language has some syntax tricks so we call it cons as well and that's what we'll use for now.

We can create lists as creating cons cells where each one receives the value of the element as A and the next cell as B in (A.B):

(cons "This" (cons "is" (cons "a" (cons "list" '()))))
;; returns => ("This" "is" "a" "list")
Enter fullscreen mode Exit fullscreen mode

As we can see the last element of each list is a nil cell '() that represents the end of the list and it's ignored when we print all elements since it's just an empty value.

Creating lists of values

Since forms are part of the structure of the language we cannot just create a list of values in code mode since Clojure evaluates every list as a form.

If we try to simply enter our list on REPL we'll get an error:

("This" "is" "a" "list")
;; returns => Execution error (ClassCastException) at user/eval2051 (REPL:1).
;; => class java.lang.String cannot be cast to class clojure.lang.IFn (java.lang.String is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'bootstrap')
Enter fullscreen mode Exit fullscreen mode

Most times you see errors about clojure.lang.IFn is because something related to functions, in this case, is because "This" can't be read as a function since Clojure expects it to be an operator that will be applied to the remaining elements.

For we create lists we need to quote them so they will be treated as data:

'("This" "is" "a" "list")
;; returns => ("This" "is" "a" "list")
Enter fullscreen mode Exit fullscreen mode

Or we can instead use the list function as we did in example above:

(list "This" "is" "a" "list")
;; returns => ("This" "is" "a" "list")
Enter fullscreen mode Exit fullscreen mode

As Clojure is a dynamic language lists can hold any given value:

'(3.1415 "PI" )
;; returns => (3.1415 "PI" \π)
Enter fullscreen mode Exit fullscreen mode

In this case, we created a list that contains a float, a string and a char.

Getting values from a list

The main thing we have to know about lists is that when we want to get a value from it we have to access all values before it. Imagine we have the following list:

'("Brock" "Misty" "Lt. Surge" "Erika")
Enter fullscreen mode Exit fullscreen mode

If we want to access "Lt. Surge" that is the third element first we have to access the two elements before it "Brock" and "Misty". This happens since it's a linked list and we don't have a direct reference to the position of each value, as the close element is to the end of the list longer will be the time needed to get its value.

To get elements we usually have three functions that we combine to go through the list: first, second, last, and rest.

first

The first function returns the element at the beginning of the list.

(first '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Brock"
Enter fullscreen mode Exit fullscreen mode

second

The second function returns the second element of the list.

(second '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Misty"
Enter fullscreen mode Exit fullscreen mode

last

The last function returns the element at the end of the list.

(last '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Erika"
Enter fullscreen mode Exit fullscreen mode

rest

The rest functions return the list without the first element.

(rest '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => ("Misty" "Lt. Surge" "Erika")
Enter fullscreen mode Exit fullscreen mode

Getting specific values

Most times when we iterate over lists we combine some of these functions to reach the desired element, let's imagine that we want to get the third element.

(first (rest (rest '("Brock" "Misty" "Lt. Surge" "Erika"))))
;; returns => "Lt. Surge"
Enter fullscreen mode Exit fullscreen mode

When we iterate over lists we usually use recursion that's something we gonna see later and help us not have to write a lot of functions to get our data, but for now is important that you know how to use and combine them.

Adding values

There are two main functions that we use to add things to a list. The first is the cons function that as we see before is used to build lists and the second is conj which is used to create a new list adding an arbitrary number of elements to the beginning of an existing one.

(conj '("Koga" "Sabrina" "Blaine" "Giovanni") "Erika" "Lt. Surge" "Misty" "Brock")
;; returns => ("Brock" "Misty" "Lt. Surge" "Erika" "Koga" "Sabrina" "Blaine" "Giovanni")
Enter fullscreen mode Exit fullscreen mode

When we use cons on other hand we build a new list with the passed exact one element on the beginning of it.

(cons "Brock" '("Misty" "Lt. Surge" "Erika"))
;; returns => ("Brock" "Misty" "Lt. Surge" "Erika")
Enter fullscreen mode Exit fullscreen mode

That's all

Now you're becoming to understand LISP a little more in the next parts we'll continue exploring some of the fundamental data structures that we can use to build our programs and how to use them to structure our data.

Top comments (0)