Clojure is a modern, dynamic, and functional dialect of the Lisp programming language on the Java platform. Like other Lisp dialects, Clojure treats code as data and has a Lisp macro system. The current development process is community-driven, overseen by Rich Hickey as its benevolent dictator for life (BDFL).
Clojure advocates immutability and immutable data structures and encourages programmers to be explicit about managing identity and its states. This focus on programming with immutable values and explicit progression-of-time constructs is intended to facilitate developing more robust, especially concurrent, programs that are simple and fast. While its type system is entirely dynamic, recent efforts have also sought the implementation of gradual typing.
Clojure has a infix notation. For instance:
(+ 1 2) ;; 1 + 2
(f 1 2) ;; f(1, 2)
(f 1 (g 1 2)) ;; f(1, g(1, 2))
Data types in Clojure.
This language use type from Java, then we have:
data in Clojure | type in Java / description |
---|---|
nil | null |
123 | java.lang.Long |
1.1 | java.lang.Double |
12N | clojure.lang.BigInt like java.math.BigInteger |
1.2N | java.math.BigDecimal |
true false | java.lang.Boolean |
“string” | java.lang.String |
\a | java.lang.Character |
#”[Rr]egexp” | java.util.regex.Pattern |
(/ 1 7) | clojure.lang.Ratio - rational number, used for maximum precision |
:keyword | like enum in Java |
#inst “2020-01-01” | java.util.Date |
Conditions
Basic expression is:
(if <condition> <then> <else>)
;; example
(if (> x 5) ;; condition
(- x 5) ;; if condition true
(- 5 x)) ;; if condition false
False values are false and nil. True values are true, “string”, “”, 77, [], [7, 8], 0 and other values.
More shorter version:
(when <condition> <then>)
;; example
(when (= x 3) ;; condition
65) ;; return if condition is true, else return nil
Also Clojure has insruction case:
(case <value>
<matching-value1> <expression1>
<matching-value2> <expression2>
...
<default>)
;; example
(case x ;; value to be compare
"v1" (+ x 1) ;; execute if x = "v1"
"v2" (- x 1) ;; execute if x = "v2"
0) ;; if x != "v1" and x != "v2",
;; if don't write this value, then throw IlligalArgumentException
If we want implement chain of conditions, then we can use “cond”:
(cond
<condition1> <expression1>
<condition2> <expression2>
...
:else <default-expression>)
;; example
(cond
(> x 0) "positive" ;; return "positeve" if x > 0
(< x 0) "negative" ;; return "negative" if x < 0
:else "neutral") ;; return "neutral" if x = 0,
;; if don't write :else, then return nil
Clojure has standard logical operations: and, or. Also all condition expressions are lazy. It means Clojure does’t evaluate an expression, until it is necessary to do so.
Functions
We can define a function with the following expressions:
(defn <function-name> [args] ;; for private function used defn-
"Documentation"
<function-body>)
;; example
(defn f [x] ;; a function with a name - "f" and one argument - "x"
"Print x and return x + 1" ;; Documentation
(println x) ;; print value x
(+ x 1)) ;; and return x + 1
Function with several arguments:
(defn sum [x y] ;; a function with two arguments
(+ x y))
(defn f [x & xs] ;; a function with an arbitary number of args
xs)
(defn f ;; define function with 0, 1 and 2 args
([] 0)
([x] 1)
([x y] 2))
Clojure has build-in support for checking pre- and postconditions:
(defn f ^{ :pre [(pos? x)] ;; if pre or post is false,
:post [(< x 50)]} ;; then throw exception
[x]
(* x 5))
For tail recursive used special word “recur”:
(defn sum [x y]
(if (x = 0)
y
(recur (dec x) (inc y))))
Anonymous functions:
((fn [x] (inc x)) 1)
#(inc %) ;; (fn [x] (inc x))
#(+ %1 %2) ;; (fn [x y] (+ x y))
#(str %1 %&) ;; (fn [x & xs] (str x xs))
Namespaces
Namespaces are mappings from simple (unqualified) symbols to Vars and/or Classes. Vars can be interned in a namespace, using def or any of its variants, in which case they have a simple symbol for a name and a reference to their containing namespace, and the namespace maps that symbol to the same var. A namespace can also contain mappings from symbols to vars interned in other namespaces by using refer or use, or from symbols to Class objects by using import. Note that namespaces are first-class, they can be enumerated etc. Namespaces are also dynamic, they can be created, removed and modified at runtime, at the Repl etc.
(ns org.marbok.clojure
(:require
[clojure.string :as str]) ;; set an alias
(:use
[clojure.core.async])) ;; trim the full name to async
Collections
;; Lists
(def l (list 1 2 3 4))
(conj l 4 5 6) ;; (6 5 4 1 2 3 4)
(nth l 2 :default) ;; 3
;; Vector
(def v [1 2 3 4])
(conj v 5 6) ;; [1 2 3 4 5 6]
(get v 10 :default) ;; default
;; Map
(def m {:x 1 [1 2 3] 2})
(sorted-map "y" 1 "x" 2) ;; {"x" 2, "y" 1}
(get m :x 6) ;; 1
(assoc m :z 3) ;; {:z 3, :x 1, [1 2 3] 2}
(dissoc m [1 2 3]) ;; {:x 1}
(contains? m :x) ;; true
;; Set
(def s #{1 2 3})
(conj s 2) ;; #{1 2 3}
(contains? s 2) ;; true
(get s 1) ;; 1
(clojure.set/difference #{1 2 3} #{3 4 5}) ;; #{1 2}
;; Equality - used only values
(= [1 2 3] '(1 2 3)) ;; true
(= #{[1 2]} #{'(1 2)}) ;; true
Collection implemented an interface “function”, then we can:
(def m { 1 "one"
2 "two"
3 "three"})
(map m (1 2 3 1)) ;; use m like function -> ("one" "two" "three" "one")
List comprehension
(for [v [[1 2] [3 4]]
x v
:let [y (+ 1 x)]
:when (even? y)
:while (< y 4)]
(+ 1 x)) ;; (2)
Exceptions
Clojure uses exceptions from java:
(try
<actions>
(catch Exception e
(handle e))
(finally
<finally-action>))
Chaining
(as-> [1 2 3 4 5 6] xs
(map inc xs)
(filter even? xs)
(remove #(> % 5) xs)
(vec xs)
(str xs)) ;; "[2 4]"
(->> [1 2 3 4 5 6]
(map inc)
(filter even?)
(remove #(> % 5))
(vec)
(str)) ;; "[2 4]"
(-> 1
(+ 1 2 3 4)
(- 2)
(str 3)) ;; "93"
;; some->> and some-> - nil-safe version
Good example:
I found a good exercise on basic language constructs: creating a very simple DBMS. All details in Github