{:check ["true"], :ranks ["eval" "references"]}

Index

Data Descriptions in Clojure

box

Why data first?

Clojure is a data-driven language (much as other function languages). Furthermore, like other variants of Lisp, Clojure's syntax has a property known as homoiconicity, which means that its own source code is a valid data structure expressed in its own language.

Scalar values

  • Numbers

    1       ; an integer
    
    3.14    ; a double
    
    123M    ; a BigDecimal
    
    3.1415M ; a BigDecimal
    
    -3.1415 ; a negative number (double)
    

    Warning: - 3.1415 is different from -3.1415.

  • Strings and characters

    "hello world"   ; a string
    
    \h              ; a character
    
  • Booleans

    true            ; boolean true
    
    false           ; boolean false
    
  • The nil value

    Unlike other languages, Clojure makes heavy usage of the nil value, and does not lightly throw errors when encountering it.

    nil
    

More scalar types specific to Clojure

  • Keywords

    Keywords are handy atomic user defined values.

    • They are hashable.
    • They only equal to themselves.
    :hello          ; a keyword
    
    :world          ; another keyword
    
    :csci-3055u     ; yet-another keyword
    
  • Symbols

    Symbols are not new to Clojure. They are just the variable names in other languages. What is new in Clojure is that symbols are also data.

    username        ; a symbol
    
    click-counter   ; a symbol
    
    f'              ; another symbol
    
    fy            ; yes this is a symbol too
    ***             ; a symbol as well - this is a valid variable name.
    

Lists and vectors

  • Lists are values enclosed in ( ... ) separated by whitespace.

    (1 2 3)
    
    ("Hello" "world" "again" "and" "again")
    
    (:hello :world :again :and :again)
    
  • Clojure lists are heterogeneous, and can contain any mixture of data types.

    ("Ready?" 3 2 1 :go!)
    
    (1 2 (3 4 ("hello" "world")))
    
    ("Canada" (:capital "Ottawa")
              (:population (36 :million))
              (:continent "North America"))
    
  • Lists are linked lists:

    Lists are implemented as linked lists. Prepending elements to lists is very efficient (constant time), but random access and appending to a list are slow (linear scan).

  • Vectors are heterogeneous dynamic arrays with fast random access:

    [1 2 3]
    
    ["Ready?" 3 2 1 :go!]
    
    ["Canada" (:capital "Ottawa")
              (:population (36 :million))
              (:content "North America")]
    

Maps and sets

Clojure is a feature rich language, especially when it comes to data description. The language provides low-cost syntax for maps and sets.

  • Maps are just a "list" of key-value pairs, enclosed in { ... }:

    {:code "CSCI 3050U"
     :title "Programming Languages"
     :instructor "Ken Pu"
     }
    
  • Maps can be deeply nested with heterogeneous data structures

    {:code "CSCI 3055U"
     :title "Programming Languages"
     :semester {:year 2020
                :season :fall}
     :instructor {:name "Ken Pu"
                  :office nil}
     :quizzes [ {:date "2020-09-01" :grade 20}
                {:date "2020-09-07" :grade 10}
                {:date "2020-09-14" :grade 20} ]
    
  • Sets are unordered lists that do not allow duplicate values. The syntax is #{ ... }.

    #{"hello" "world" "again"}
    
  • All Clojure data types support equality comparison. So, sets can contain data structures as well.

    #{ {:code "CSCI 3055U" :title "Programming Languages"}
       {:code "CSCI 2000U" :title "Scientific Data Analysis"} }
    

Eval

Evaluation

Before we delve into the deep end of Clojure programming, we need to understand the lifecycle of Clojure data expressions.

  • Code as data

    Computation is described as Clojure data. This is known as the homoiconic property of a programming language. Thus:

    box
    • Clojure source code is strictly a Clojure data structure.
    • When data is code, it goes through evaluation. which consists of two phases: reader and execution.
     +-------+   reader  +-------------+  execution   +--------+
     | Data  | --------> | Computation | -----------> | Result |
     +-------+           +-------------+              +--------+ 
    
  • Data as code

    Only lists and symbols are evaluated as computation. All other types of values are considered as data.

    All forms of computational constructs are expressed as lists. While Clojure's syntax is simple and small, through declaration using lists, Clojure offeres a rich set of computational models.

    functional programming, lazy evaluation, concurrency, polymorphism.

  • Data as data

    It's possible to suppress evaluation of lists using the quoted form.

    '(+ 1 2)
    ; => (clojure.core/+ 1 2)
    
    (+ 1 2)
    ; => 3
    
    'x
    ; => x
    
    x
    ; => Unable to resolve symbol: x in this context
    
  • Quotes are recursively applied.

    You just need to quote an expression and all nested lists are also quoted.

    '{:count (+ 1 2)
      :title (str "Hello " "World")}
    ; => {:count (+ 1 2) :title (str "Hello " "World")}
    
    {:count (+ 1 2)
     :title (str "Hello " "World")}
    ; => {:count 3 :title "Hello World")}
    

References

References

  1. This material is based on Programming Clojure: Section 2.1 and 2.2