Module 1 - Tutorial - Basic Clojure

{:deps {org.clojure/clojure {:mvn/version "1.10.3"}
        ;; complient is used for autocompletion
        ;; add your libs here (and restart the runtime to pick up changes)
        compliment/compliment {:mvn/version "0.3.9"}}}
Extensible Data Notation
{:hello (clojure-version)}
0.0s

Binding values to variables

The value types that we will be using in this course are mainly int, float, and string values.

  • An int value is an integer (whole numbers)

  • A float value is a decimal number

  • A string value is made up for zero or more characters that may be symbols, whitespace, letters, or numbers. Strings are always wrapped in double quotes.

  • The semi-colon (;) sign denotes comments, which is not read by Clojure as code.

(def my-age 35) ; integer
(def my-temp 98.7) ; float
(def greeting "Hello World!") ; string
0.0s
; Use the `println` function to see the output of the values bound to the variables
(println my-age)
(println my-temp)
(println greeting)
0.4s

Note: Nextjournal notebooks can display the output of a variable's value by typing the variable name, but only will output the value of the last variable written in a cell:

my-age ; this will not display
my-temp ; this will display because it's the last item in the code cell
0.0s
; the "type" function will tell you what data type a variable is
(println (type greeting))
0.3s
; [extra optional info] you can also use the "class" function, 
;; which will give a similar result to "type"
(println (class greeting))
0.2s
; variables can be added together
(println (+ my-age my-temp))
; this does not work the same for strings
; uncomment the line below to see what happens
; (println (+ "pine" "apple")) ; results in an error
; in order to combine (technical term "concatenate") strings, use the str function
(println (str "pine" "apple"))
0.3s

Special Form if

  • Checks if a condition is true

  • If the condition is true, then the first form is evaluated. Else, the second form is evaluated.

(if (= my-age 35)
  (println "I am 35 years old.")
  (println "I am not 35 years old."))
0.2s

Lists and Iteration

  • A list is a container for multiple values

  • A map goes over each item within a list and evaluates a function with each item as an argument to said function, this is especially useful for transforming/changing list elements themselves

  • A loop iterates (repeats itself) as long as its repeating condition is true, which can be useful for when you don't know in advance how many iterations there will be

  • A list comprehension can be useful for generating new lists as well as printing out lists

; this is a list with three items
(def colors '("red" "yellow" "blue"))
0.0s
List Comprehension
; by using list comprehension, we can print out all of the colors
(doseq [c colors]
  (println c))
0.3s
Mapping
;; by using map, we can transform all the colors to uppercase, ...
(println (map clojure.string/upper-case colors))
;; ... count the number of letters in each color, ...
(println (map count colors))
; ... get the first letter in each word, ...
(println (map first colors))
;; ... or even reverse the letters in each word!
(println (map #((comp clojure.string/join reverse) %) colors))
0.3s
; by using a loop, we can iterate as many times as the repeating condition is true
(loop [i 0] ; i is our 'counter' and it starts at 1
  (when (< i 5) ; as long as i is less than 5,
    (print i) ; we will print out i,
    (recur (inc i)))) ; and then repeat, passing in a new counter which is incremented by 1
0.2s

The count function returns the length (number of) items in a countable object, such as a string or collection.

(println (count '(0 1 2 3 4)))
(println (count colors))
(println (count "Hello World!"))
0.3s

There is another collection type in Clojure which is called a "vector". Vectors generally allow for faster mutations, slicing, and random access (via the nth function).

; this vector has 7 items
; position index starts at 0 and ends at 6
(def days-vector ["Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"])
(println (subvec days-vector 2 6)) ; the 'start'ing number is inclusive, the 'end'ing number is not
0.2s

Maps

  • Similar to lists, maps are a collection type to store values

  • Items in maps are stored with a key:value pair (like a physical dictionary - you look up a word (key) and find the definition (value))

  • Instead of getting values by their positions, values are called by their key name

; maps are written using curly braces
(def pet-ages {:goku 5
                :mika 3
                :brunson 12
                :lucas 3
                :genki 1})
; keys in a map must be unique, but values need not be 
(def pet-breeds {:goku "Pomeranian"
                  :mika "Shiba"
                  :brunson "Manx"
                  :lucas "Tabby"
                 :genki "Goldfish"})
; the values themselves can also be keywords
(def pet-animals {:goku :dog
                  :mika :dog
                  :brunson :cat
                  :lucas :cat
                 :genki :fish})
0.0s
; to see what kind of dog Mika is, look up the key :mika in the map 'pet-breeds
(pet-breeds :mika)
0.0s
; see an entire map at once
(println pet-ages)
0.2s
; list of keys
(keys pet-breeds)
0.0s
; list of values
(vals pet-ages)
0.0s
; create a new map from a pre-existing map by 'adding' a new key value pair to it
(conj pet-animals {:marshmello :turtle}) ; conj stands for 'conjoin'
0.0s
; iterating over a map to print out both the keys and values.
(doseq [[key val] pet-animals] ; for each key value pair in pet-animals
  (println (str (clojure.string/capitalize (name key))
             " is a " (name val) "."))) ; print out both together in a sentence
0.3s

Defining your own functions with defn

  • The first word is defn, this tells Clojure we are about to make a new function

  • The second word will be the name of our function, it's what we will use later to "call" or "invoke" our function and make it do work

  • The third thing we will write is a vector which takes in one or more "function arguments" - these will be the data inputs that our function will need in order to work. Note that some functions do not take any arguments, so they will simply just get an empty vector like this: []

  • Lastly, after the arguments list, we can define the body of the function, doing work such as mathematical operations, printing out to the console, or transforming or creating data.

Note that the result of the body of the function is always returned, even if there is nothing to return (for example, when we use println, we get back nothing, which is, in Clojure, called nil). Also, a function definition is just that, a definition - it doesn't do any work until it is directly called.

; here is an example where I've made an 'add' function for you
(defn add [num-a num-b]
  (+ num-a num-b))
; and we can use it like this:
(add 4 7)
0.2s
; here's a more advanced example:
; let's find out how old Goku and Mika are in dog years
(defn human-2-dog-year [years]
  (case years
    0 0 ; if years is 0, then we return 0
    1 15 ; if years is 1, then we return 15
    2 24 ; and so on
    
    ; this is our 'default' case, where, if none of the cases 
    ; above were matched, then we evaluate this line
    (+ 24 (* 5 (- years 2))) ; How would you read this line out in plain language?
    ))
  
(println (str "Goku is " (human-2-dog-year (pet-ages :goku)) " years old in dog years."))
(println (str "Mika is " (human-2-dog-year (pet-ages :mika)) " years old in dog years."))
1.0s
Runtimes (1)