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")
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.
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"
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")
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')
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")
Or we can instead use the list function as we did in example above:
(list "This" "is" "a" "list")
;; returns => ("This" "is" "a" "list")
As Clojure is a dynamic language lists can hold any given value:
'(3.1415 "PI" \π)
;; returns => (3.1415 "PI" \π)
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")
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"
second
The second function returns the second element of the list.
(second '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Misty"
last
The last function returns the element at the end of the list.
(last '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Erika"
rest
The rest functions return the list without the first element.
(rest '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => ("Misty" "Lt. Surge" "Erika")
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"
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")
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")
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)