This site runs best with JavaScript enabled.

Day 4: Brave Clojure Chapter 3 - Do things

Zac Jones

August 05, 2020


Photo by Ingmar on Unsplash

These are my notes from Clojure for the Brave and True by Daniel Higginbotham. They are intended for the #learninpublic run that I'm doing with ClojureFam (Team Phanes)

These are notes for chapter 3 Do Things: A Clojure Crash Course

Table of Contents

  1. Chapter 3: A Clojure Crash Course
    1. Forms
    2. Control Flow
    3. Naming Values with def
    4. Data Structures
      1. Numbers
      2. Strings
      3. Maps
      4. Keywords
      5. Vectors
      6. Lists
      7. Sets
      8. Simplicity
    5. Functions
      1. Calling functions
      2. Function Calls, Macro Calls, and Special Forms
      3. Defining Functions
      4. Arity
      5. Destructuring
      6. Function body
      7. Anonymous Functions
    6. Returning Functions
    7. Shire model

Chapter 3: A Clojure Crash Course

Forms

Clojure recognizes two kinds of structures:

  1. Literal representations of data structures

  2. Operations

  3. Form: valid code

  4. Expression: Clojure forms

Clojure evaluates every form to produce a value

You use literals in operations

(+ 1 2 3)
; => 6

Control Flow

  • Boolean form: a form that evaluates to a truthy or falsey value.
    (if true
    "By Zeus's hammer!"
    "By Aquaman's trident!")

Control flow branches in Clojure are implicit. The first is true, the second is false. In other words. first is then, next is else

The else branch is optional.

Each branch can only have one Form normally. You can get around this with the do operator.

(if true
(do (println "Success!")
"By Zeus's hammer!")
(do (println "Failure!")
"By Aquaman's trident!"))

when operator is a combination of if and do. You can run multiple commands but it doesn't have an else branch

  • nil: indicates no value in Clojure You can check if nil with nil? Both nil and false are used to represent logical falsiness Operators go first in Clojure forms so:
    (= 1 1)
    Clojure has Boolean operators:
    • or: returns either the first truthy value or the last value
    • and: returns the first falsey value or, if no values are falsey, the last truthy value

Naming Values with def

  • def: an operator to bind a name to a value in Clojure bind is intentional language here because we don't re-define variables once they are bound. Other languages might re-assign variables multiple times.

    (def failed-protagonist-names ["Larry Potter" "Doreen the Explorer" "The Incredible Bulk"])

    failed-protagonist-names

Data Structures

Numbers

coercion and contagion in clojure?

93
1.2
1/5

Strings

"Lord Voldemort"
"\"He who must not be named\""
"\"Great cow of Moscow!\" - Hermes Conrad"

Single quote's don't count as strings! No string interpolation!

str concatinates strings

(def name "Chewbacca")
(str "\"Uggllglglglglglglglll\" - " name)

Maps

Two kinds of maps, hash maps and sorted maps

{} is a map literal

{:first-name "Charlie"
:last-name "McFishwich"}

You can associate string keys to functions

{"string-key" +}

Maps can be nested Values can be any type - clojure don't care!

hash-map will create a map for you

(hash-map :a 1 :b 2)

get looks up values

(get {:a 0 :b 1} :b)

get-in looks up values in nested maps You can access values in a map by treating it like a function. The keys are it's arguments

({:name "The Human Coffeepot"} :name)

Keywords

Keywords are primarily used in maps. they can be used as functions that look up corresponding value in a data structure. They accept defaults just like get

People us this all the time!

Vectors

Vectors are similar to arrays. They are 0-base indexed.

(get ["a" {:name "Pugsley Winterbottom"} "c"] 1)

You can use the vector function to create one. or the vector literal []

conj function adds elements to the vector

Lists

Lists are linear collections of values. You can't retrieve elements in a list with get.

list literal: '(1 2 3 4)

If you want to retrieve an element from the list you can use the nth function

(nth '(:a :b :c) 0)
(nth '(:a :b :c) 2)

Sets

Clojure has two kinds of sets: hash sets and sorted sets

hash set literal: #{} or use the hash-set function to create a set

Sets only allow unique values You can convert vectors to sets with the set function

you can check for set membership by using contains? in tandem with get

(contains? #{:a :b} :a)
; => true
(contains? #{:a :b} 3)
; => false
(contains? #{nil} nil)

Simplicity

It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. —Alan Perlis

Functions

Calling functions

all Clojure operations have the same syntax: opening parenthesis, operator, operands, closing parenthesis. Function call is just another term for an operation where the operator is a function or a function expression (an expression that returns a function).

(+ 1 2 3 4)
(* 1 2 3 4)
(first [1 2 3 4])
(1 2 3 4)
("test" 1 2 3)

When you call a number or string as a function you'll get the error above. This will happen alot!

  • higher-order functions: functions that can either take a function as an argument or return a function This is 'first-class' function support

    (map inc [0 1 2 3])

Map applies inc to each value in the vector. map allows you to generalize the process of transforming a collection by applying a function—any function—over any collection.

Clojure evaluates all function arguemnts recursively before passing them to the function.

Function Calls, Macro Calls, and Special Forms

  • Function Call: expressions that have a function expression as the operator
  • Macro calls: evaluate their operands differently (but how?!) and can't be passed as arguments to functions
  • Special forms: don't evaluate all their operands (think if expressions). You can't use special forms as arguments to functions.

Defining Functions

The 5 main parts:

  • defn

  • Function name

  • docstring: describing the function (optional). use (doc fn-name) to see the function doc

  • Parameters (and Arity): listed in brackets (vector). The number of parameters is the level of arity.

  • Function body

    (def my-special-multiplier (fn [x] ( x 3))) (defn def-my-special-multiplier [x] ( x 3))

    (my-special-multiplier 2) (def-my-special-multiplier 4)

Arity

  • arity overloading: define a function so a different function body will run depending on the arity. each definition will be closed in parens and have an argument list.

    Arity overloading is one way to provide default values for arguments.

    (defn x-chop "Describe the kind of chop you're inflicting on someone" (name chop-type) ([name] (x-chop name "karate")))

    (x-chop "Kanye West" "slap") (x-chop "Kanye West")

You can provide variable arity to a function with the rest parameter: &

Destructuring

  • destructuring: Lets you concisely bind names to values within a collection

    ;; Return the first element of a collection (defn my-first [[first-thing]] ; Notice that first-thing is within a vector first-thing)

    (my-first ["oven" "bike" "war-axe"])

You can name as many arguments as you want and use the rest operator to pass the... rest of the list

You can destructure keywords out of maps as well.

(defn receive-treasure-location
[{:keys [lat lng] :as treasure-location}]
(println (str "Treasure lat: " lat))
(println (str "Treasure lng: " lng))
;; One would assume that this would put in new coordinates for your ship
(steer-ship! treasure-location))

Function body

The function body contains forms of any kind. It will return the last form evaluated.

Anonymous Functions

Create Anonymous functions in 2 ways:

  • fn treat fn almost identically to how you treat defn they can be associated with a name as well!
  • #(...) more compact version this is possible by what Clojure calls reader macros. % indicates Arguments %1, %2, %3 indicates multiple arguments %& passes the rest of the arguments

Returning Functions

  • Returned functions are closures, they can access all the variables that were in scope when the function was created
    (defn inc-maker
    "Create a custom incrementor"
    [inc-by]
    #(+ % inc-by))
    (def inc3 (inc-maker 3))
    (inc3 7)

Shire model

(def asym-hobbit-body-parts [{:name "head" :size 3}
{:name "left-eye" :size 1}
{:name "left-ear" :size 1}
{:name "mouth" :size 1}
{:name "nose" :size 1}
{:name "neck" :size 2}
{:name "left-shoulder" :size 3}
{:name "left-upper-arm" :size 3}
{:name "chest" :size 10}
{:name "back" :size 10}
{:name "left-forearm" :size 3}
{:name "abdomen" :size 6}
{:name "left-kidney" :size 1}
{:name "left-hand" :size 2}
{:name "left-knee" :size 2}
{:name "left-thigh" :size 4}
{:name "left-lower-leg" :size 3}
{:name "left-achilles" :size 1}
{:name "left-foot" :size 2}])
(defn sym-hobbit
[asym]
(map (fn [{name :name}] (if (clojure.string/includes? (str name) "left")
(println "yay")
()
)) asym))
(sym-hobbit asym-hobbit-body-parts)
Share article