Learn ClojureScript in Y Minutes

ClojureScript is a dialect of the Clojure language developed for JavaScript environments. It has most of the functionality of Clojure with small adaptations owing to the differences in the underlying platform.

ClojureScript is a dialect of the Clojure language developed for JavaScript environments. It has most of the functionality of Clojure with small adaptations owing to the differences in the underlying platform.

All you need is a ClojureScript REPL.

; Comments start with semicolons.

; ClojureScript is written in "forms", which are just
; lists of things inside parentheses, separated by whitespace.
;
; The clojure reader assumes that the first thing is a
; function or macro to call, and the rest are arguments.

; The first call in a file should be ns, to set the namespace
(ns learn.core)

; More basic examples:

; str will create a string out of all its arguments
(str "Hello" " " "World") ; => "Hello World"

; Math is straightforward
(+ 1 1) ; => 2
(- 2 1) ; => 1
(* 2 2) ; => 4
(/ 1 2) ; => 0.5 (in Clojure, this would return `1/2`)

; Equality is =
(= 1 1) ; => true
(= 2 1) ; => false

; You need not for logic, too
(not true) ; => false

; Nesting forms works as you expect
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2

; Types
;;;;;;;;;;;;;

; ClojureScript uses JavaScript's types for booleans, strings and numbers.
; Use `type` to inspect them.
(type 1) ; => #object[Number "function Number() { [native code] }"]
(type 1.) ; Same as above, no distinction between int and double
(type "") ; Strings always double-quoted
(type false) ; => #object[Boolean "function Boolean() { [native code] }"]
(type nil) ; The "null" value is called nil

; If you want to create a literal list of data, use ' to stop it from
; being evaluated
'(+ 1 2) ; => (+ 1 2)
; (shorthand for (quote (+ 1 2)))

; Collections & Sequences
;;;;;;;;;;;;;;;;;;;

; Lists are linked-list data structures, while Vectors are array-backed.
(type [1 2 3]); => cljs.core/PersistentVector
(type '(1 2 3)); => cljs.core/List

; A list would be written as just (1 2 3), but we have to quote
; it to stop the reader thinking it's a function.
; Also, (list 1 2 3) is the same as '(1 2 3)

; "Collections" are just groups of data
; Both lists and vectors are collections:
(coll? '(1 2 3)) ; => true
(coll? [1 2 3]) ; => true

; "Sequences" (seqs) are abstract descriptions of lists of data.
; Only lists are seqs.
(seq? '(1 2 3)) ; => true
(seq? [1 2 3]) ; => false

; A seq need only provide an entry when it is accessed.
; So, seqs which can be lazy -- they can define infinite series:
(range 4) ; => (0 1 2 3)
(range) ; => (0 1 2 3 4 ...) (an infinite series, don't evaluate!)
(take 4 (range)) ;  (0 1 2 3)

; Use cons to add an item to the beginning of a list or vector
(cons 4 [1 2 3]) ; => (4 1 2 3)
(cons 4 '(1 2 3)) ; => (4 1 2 3)

; Conj will add an item to a collection in the most efficient way.
; For lists, they insert at the beginning. For vectors, they insert at the end.
(conj [1 2 3] 4) ; => [1 2 3 4]
(conj '(1 2 3) 4) ; => (4 1 2 3)

; Use concat to add lists or vectors together
(concat [1 2] '(3 4)) ; => (1 2 3 4)

; Use filter, map to interact with collections
(map inc [1 2 3]) ; => (2 3 4)
(filter even? [1 2 3]) ; => (2)

; Use reduce to reduce them
(reduce + [1 2 3 4])
; = (+ (+ (+ 1 2) 3) 4)
; => 10

; Reduce can take an initial-value argument too
(reduce conj [] '(3 2 1))
; = (conj (conj (conj [] 3) 2) 1)
; => [3 2 1]

; Functions
;;;;;;;;;;;;;;;;;;;;;

; Use fn to create new functions. A function always returns
; its last statement.
(fn [] "Hello World") ; => fn

; (You need extra parens to call it)
((fn [] "Hello World")) ; => "Hello World"

; You can create a var using def
(def x 1)
x ; => 1

; Assign a function to a var
(def hello-world (fn [] "Hello World"))
(hello-world) ; => "Hello World"

; You can shorten this process by using defn
(defn hello-world [] "Hello World")

; The [] is the list of arguments for the function.
(defn hello [name]
  (str "Hello " name))
(hello "Steve") ; => "Hello Steve"

; You can also use this shorthand to create functions:
(def hello2 #(str "Hello " %1))
(hello2 "Fanny") ; => "Hello Fanny"

; You can have multi-variadic functions, too
(defn hello3
  ([] "Hello World")
  ([name] (str "Hello " name)))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"

; Functions can pack extra arguments up in a seq for you
(defn count-args [& args]
  (str "You passed " (count args) " args: " args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"

; You can mix regular and packed arguments
(defn hello-count [name & args]
  (str "Hello " name ", you passed " (count args) " extra args"))
(hello-count "Finn" 1 2 3)
; => "Hello Finn, you passed 3 extra args"


; Maps
;;;;;;;;;;

; Hash maps and array maps share an interface. Hash maps have faster lookups
; but don't retain key order.
(type {:a 1 :b 2 :c 3}) ; => cljs.core/PersistentArrayMap
(type (hash-map :a 1 :b 2 :c 3)) ; => cljs.core/PersistentHashMap

; Arraymaps will automatically become hashmaps through most operations
; if they get big enough, so you don't need to worry.

; Maps can use any hashable type as a key, but usually keywords are best
; Keywords are like strings with some efficiency bonuses
(type :a) ; => cljs.core/Keyword

(def stringmap {"a" 1, "b" 2, "c" 3})
stringmap  ; => {"a" 1, "b" 2, "c" 3}

(def keymap {:a 1, :b 2, :c 3})
keymap ; => {:a 1, :c 3, :b 2}

; By the way, commas are always treated as whitespace and do nothing.

; Retrieve a value from a map by calling it as a function
(stringmap "a") ; => 1
(keymap :a) ; => 1

; Keywords can be used to retrieve their value from a map, too!
(:b keymap) ; => 2

; Don't try this with strings.
;("a" stringmap)
; => #object[TypeError TypeError: "a".call is not a function]

; Retrieving a non-present key returns nil
(stringmap "d") ; => nil

; Use assoc to add new keys to hash-maps
(def newkeymap (assoc keymap :d 4))
newkeymap ; => {:a 1, :b 2, :c 3, :d 4}

; But remember, clojure types are immutable!
keymap ; => {:a 1, :b 2, :c 3}

; Use dissoc to remove keys
(dissoc keymap :a :b) ; => {:c 3}

; Sets
;;;;;;

(type #{1 2 3}) ; => cljs.core/PersistentHashSet
(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}

; Add a member with conj
(conj #{1 2 3} 4) ; => #{1 2 3 4}

; Remove one with disj
(disj #{1 2 3} 1) ; => #{2 3}

; Test for existence by using the set as a function:
(#{1 2 3} 1) ; => 1
(#{1 2 3} 4) ; => nil

; There are more functions in the clojure.sets namespace.

; Useful forms
;;;;;;;;;;;;;;;;;

; Logic constructs in clojure are just macros, and look like
; everything else
(if false "a" "b") ; => "b"
(if false "a") ; => nil

; Use let to create temporary bindings
(let [a 1 b 2]
  (> a b)) ; => false

; Group statements together with do
(do
  (print "Hello")
  "World") ; => "World" (prints "Hello")

; Functions have an implicit do
(defn print-and-say-hello [name]
  (print "Saying hello to " name)
  (str "Hello " name))
(print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff")

; So does let
(let [name "Urkel"]
  (print "Saying hello to " name)
  (str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")


; Use the threading macros (-> and ->>) to express transformations of
; data more clearly.

; The "Thread-first" macro (->) inserts into each form the result of
; the previous, as the first argument (second item)
(->  
   {:a 1 :b 2}
   (assoc :c 3) ;=> (assoc {:a 1 :b 2} :c 3)
   (dissoc :b)) ;=> (dissoc (assoc {:a 1 :b 2} :c 3) :b)

; This expression could be written as:
; (dissoc (assoc {:a 1 :b 2} :c 3) :b)
; and evaluates to {:a 1 :c 3}

; The double arrow does the same thing, but inserts the result of
; each line at the *end* of the form. This is useful for collection
; operations in particular:
(->>
   (range 10)
   (map inc)     ;=> (map inc (range 10)
   (filter odd?) ;=> (filter odd? (map inc (range 10))
   (into []))    ;=> (into [] (filter odd? (map inc (range 10)))
                 ; Result: [1 3 5 7 9]

; Modules
;;;;;;;;;;;;;;;

; Use require to import a module
(require 'clojure.string)

; Use / to call functions from a module
; Here, the module is clojure.string and the function is blank?
(clojure.string/blank? "") ; => true

; You can give a module a shorter name on import
(require '[clojure.string :as str])
(str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."
; (#"" denotes a regular expression literal)

; You can choose a subset of functions to import, too
(require '[clojure.set :refer [intersection]])

; Now you can use `intersection` directly
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3}

; You can use require from a namespace using :require.
; You don't need to quote your modules if you do it this way.
(ns test.core
  (:require [clojure.string :as str]
            [clojure.set :as set]))

; JavaScript
;;;;;;;;;;;;;;;;;

; JavaScript has a huge and ecosystem, so
; you'll want to learn how to get at it.

; Prefix with `js/` and use the class name with a `.` at the end
; to make a new instance. Or use `new`
(js/Date.) ; JS: new Date()
(new js/Date) ; JS: new Date()

; Use `.` to call methods.
; Or, use the `.method` shortcut
(. (js/Date.) getTime) ; <a timestamp>
(.getTime (js/Date.)) ; exactly the same thing.

; You can access properties via `.` as well.
; Or use the `.-property` shortcut
(. js/Math -PI) ; JS: Math.PI
(.-PI js/Math) ; JS: Math.PI

; You can access properties and call functions in nested objects via `..`
(.. js/self -Math -PI) ; JS: self.Math.PI
(.. js/self -Math (pow 10 2)) ; JS: self.Math.pow(10, 2)

; For methods you can use the `.method` shortcut, again
(.Math.pow js/self 10 2) ; JS: self.Math.pow(10, 2)

; You can tag a clojure map literal with `#js` to turn it into json
; Don't forget the inner `#js` tag!
#js {:a 1 :n #js {:b 2 :c 3}} ; JS: {"a": 1, "n": {"b": 2, "c": 3}}

; For symbolic references use `clj->js`
(def dat {:a 1 :n {:b 2 :c 3}})
(clj->js dat) ; JS:  {"a": 1, "n": {"b": 2, "c": 3}}

; To turn json into clojure data structures, use `js->clj`
(def jsdat (clj->js dat))
(js->clj jsdat) ; => {"a" 1, "n" {"b" 2, "c" 3}}
(js->clj jsdat :keywordize-keys true) ; => {:a 1, :n {:b 2, :c 3}}

; You can set properties on a js object via `set!`
; Note that cljs functions are also js functions

(set! (.-onclick js/document) (fn [e] (.log js/console e)))
; JS: document.onclick = function(e) { console.log(e) }

(set! (.. js/self -document -onclick) #(.log js/console %))
; JS: self.document.onclick = function(e) { console.log(e) }

; STM
;;;;;;;;;;;;;;;;;

; Software Transactional Memory is the mechanism clojure uses to handle
; persistent state. There are a few constructs in clojure that use this.

; An atom is the simplest. Pass it an initial value
(def my-atom (atom {}))

; Update an atom with swap!.
; swap! takes a function and calls it with the current value of the atom
; as the first argument, and any trailing arguments as the second
(swap! my-atom assoc :a 1) ; Sets my-atom to the result of (assoc {} :a 1)
(swap! my-atom assoc :b 2) ; Sets my-atom to the result of (assoc {:a 1} :b 2)

; Use '@' to dereference the atom and get the value
my-atom  ;=> Atom<#...> (Returns the Atom object)
@my-atom ; => {:a 1 :b 2}

; Here's a simple counter using an atom
(def counter (atom 0))
(defn inc-counter []
  (swap! counter inc))

(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)

@counter ; => 5

; Other STM constructs are refs and agents.
; Refs: http://clojure.org/refs
; Agents: http://clojure.org/agents

Further Reading

This is far from exhaustive, but hopefully it’s enough to get you on your feet.

ClojureScript Project Home: https://github.com/clojure/clojurescript

My Screencasts on YouTube: https://www.youtube.com/playlist?list=PLJUDspZJoglqlVb4PZ-zktOaQrUlQxi8H


© 2024 Florian Klampfer

Powered by Hydejack v9.2.0