Translations: 中文.
If you just wanna try something, check out the interactive tutorial.
Hello, this is my attempt at a very concise guide to ClojureScript's syntax! ClojureScript is a Lisp dialect for front-end web development. It compiles to JavaScript for use in the browser.
ClojureScript is fundamentally different from JavaScript and other compile-to-JS languages like Dart, CoffeeScript, and TypeScript. It uses a more powerful yet simpler syntax. There are other differences not related to syntax, such as default immutability to combat the "new spaghetti code" that is mutatable stateful objects, and sane state management allowing language-level data-binding.
I believe that ClojureScript's largest barrier to entry for beginners is probably the foreign nature of its syntax. I hope to explain it as plainly and succinctly as possible with this guide.
Also, check out Parinfer if you want a simpler way to manage parentheses in ClojureScript.
There is literal data:
; number
1.23
; string
"foo"
; keyword (like strings, but used as map keys)
:foo
; vector (array)
[:bar 3.14 "hello"]
; map (associative array)
{:msg "hello" :pi 3.14 :primes [2 3 5 7 11 13]}
; set (distinct elements)
#{:bar 3.14 "hello"}
And there is symbolic data:
; symbol (represents a named value)
foo
; list (represents a "call")
(foo :bar 3.14)
ClojureScript can evaluate data to create a new "value" from it.
-
Literal data evaluates to itself, of course:
1.23 ; => 1.23 "foo" ; => "foo" [:bar 3.14 "hello"] ; => [:bar 3.14 "hello"]
-
A symbol evaluates to the value bound to it:
foo ; => 3
-
A list evaluates to the return value of a "call".
(+ 1 2 3) ; => 6 (= 1 2) ; => false (if true "y" "n") ; => "y"
If the first element of a list is a function, then the rest of the elements are evaluated and passed to it (prefix notation).
; String concatenate function
(str "Hello " "World") ; => "Hello World"
; Arithmetic functions
(= a b) ; equality (true or false)
(+ a b) ; sum
(- a b) ; difference
(* a b c) ; product
(< a b c) ; true if a < b < c
; Evaluation Steps
(+ k (* 2 4)) ; assume k evalutes to 3
(+ 3 (* 2 4)) ; (* 2 4) evaluates to 8
(+ 3 8) ; (+ 3 8) evalutes to 11
11
If the first element of a list is one of the language's few special forms, then the rest of the elements are passed to it unevaluated. (There are only 22 special forms.)
(if (= a b c) ; <-- determines if a=b=c
(foo 1) ; <-- only evaluated if true
(bar 2) ; <-- only evaluated if false
)
; define k as 3
(def k 3) ; <-- notice that k is not evaluated here
; (def needs the symbol k, not its value)
; make a greeting function
(fn [username] ; <-- expected parameters vector
(str "Hello " username))
; oops, give the function a name
(def greet (fn [username]
(str "Hello " username)))
(greet "Bob") ; => "Hello Bob"
If the first element of a list is a macro, then the rest of the elements are passed to it unevaluated, but the resulting value of the call is evaluated. Let's illustrate that difference with the following diagram:
This difference in evaluation allows macros to act as code-generating
functions. For example, the defn
macro expands to def
and fn
, as we used
separately in a previous example:
; create a named function using the defn macro
(defn greet [username]
(str "Hello " username))
; the definition for the defn macro (over-simplified)
(defmacro defn [name args body]
`(def ~name (fn ~args ~body)))
App developers rarely need to create their own macros, but it is an indispensible tool for the library developer to give app developers the full flexibility of the language.
There are a few macro characters that help make the language succinct by performing simple substitutions (not full macros):
; short-hand for creating a simple function:
; #(...) => (fn [args] (...))
#(* 3 %) ; => (fn [x] (* 3 x))
#(* 3 (+ %1 %2)) ; => (fn [x y] (* 3 (+ x y)))
You need to know more than syntax to be proficient in a language, of course. But you should now know enough to be comfortable looking around at examples and reasoning about how data is being evaluated and passed around:
; printing to the javascript console
(js/console.log "Hello World!")
; creating local bindings (constants)
(let [a (+ 1 2)
b (* 2 3)]
(js/console.log "The value of a is" a)
(js/console.log "The value of b is" b))
; generate a sequence of numbers
(range 4) ; => (0 1 2 3)
; generate first four multiples of 3
(map #(* % 3) (range 4)) ;=> (0 3 6 9)
; count elements in a sequence
(count "Bob") ; => 3
(count [4 5 2 3]) ; => 4
; select three letter names from a list
(def names ["Bob" "David" "Sue"])
(filter #(= (count %) 3) names) ; => ("Bob" "Sue")
(Don't worry about getting lost in parentheses. All modern text editors will highlight the corresponding ones and will indent your code automatically for readability, as is standard in every other language.)
See Jumping from HTML to ClojureScript to see how ClojureScript's syntax solves the verbosity/flexibility problems faced in the JS community by JSX.
The syntax section of the ClojureScript API reference is a comprehensive look at the possible syntax forms and even shows the source code for how each are read/parsed.
Here are the resources and steps that I took while learning ClojureScript. (Most resources covering Clojure also apply to ClojureScript, since they share a significant subset with each other.)
- Reading through the ClojureScript Wiki
- Reading the book ClojureScript Up and Running
- Reading the book Clojure Programming
- Doing ClojureScript Koans
- Reading Clojure Style Guide
- Reading Clojure Programming By Example
- Reading Clojure Functional Programming
- Thumbing through Clojure Core API
- Reading ClojureScript - Differences from Clojure - Host Interop for accessing javascript properties like
(.-Infinity js/window)
and functions like(.sqrt js/Math 25)
. - Reading JavaScript to ClojureScript synonyms
- Experimenting in
lein repl
for Clojure REPL. - Experimenting in http://clojurescript.net/ for ClojureScript REPL with a browser context.
- Reading docstrings of functions I encounter with
(doc <funcname>)
in REPL. - Miscellaneous ClojureScript things to know