ClojureScript is the Clojure compiler for JavaScript. With it, you can use a dialect of Clojure, called ClojureScript, which is 99% identical in syntax and semantics to Clojure, to write your JavaScript code. So wherever you used to use JavaScript, you can now use Clojure instead!
Not sure what ClojureScript has to offer? Well this is the overview for you.
Please note that this list was put together at the time of ClojureScript 1.10. If you are using an older version, some features might be missing, and if you are using a newer version, there might be newer features not mentioned here. You can refer to the official changes list for all the gritty details.
ClojureScript offers some of the following features:
- Proper modules
- Lambda functions
- Protocol based OOP
- Large standard library
- Multiline and template strings
- Destructuring
- Rest, Named, Spread and Overloaded arguments
- Proper scopes
- Iterators and Lazy Sequences
- Unicode support
- Immutable data structures
- Mutable data structures
- Controlled mutable values
- Type extension
- Syntax extension
- Many more
Proper modules
Language-level support for modules. This lets you define logical components with clear dependencies on one another. In ClojureScript, we call them namespaces, and you create them with ns
. ClojureScript namespaces are compatible with Google Closure modules too, so you can use those as is. It also has native support to convert CommonJS, AMD, ES6 and Node modules to Google Closure modules, only a few lines of configs are required for the conversion, you can read more about it here.
(ns cljs-features-demo
(:require [goog.string :as gstr]
goog.string.format))
;; You can require other modules, such as the standard Google Closure
;; library, which is part of ClojureScript's standard library.
;; Other modules can now require the use of `cljs-features-demo`.
;; `:as` was used to create an alias, so we can use `gstr` instead of
;; `goog.string` to access functions in `goog.string`.
Lambda functions
Lambda functions are a way to declare a function inline, and with minimal syntax. Generally, they don't have names, though they can. They are similar to Java 8, C# and JavaScript arrow's syntax declared with =>
, except they are declared with a different syntax in ClojureScript, respectively either using #()
or fn
. The former is shorter to type, but the latter has more features. Both support full closure, and will therefore capture the global and local context along with them.
;;; With shorthand `#()` syntax
(remove #(= % 10) [2 4 8 10 12])
;;=> [2 4 8 12]
;; %, %2, %3, %... are the arguments
;;; With custom named arguments using `fn` syntax
(remove (fn[v] (= v 10)) [2 4 8 10 12])
;;=> [2 4 8 12]
Protocol based OOP
ClojureScript is a functional programming language first and foremost, yet it allows you to do much the same things that you would do using JavaScript's objects and prototypes. Protocols define a new interface, and any type that implements them will have an implementation for the methods they define. Records are a named bundle of properties, much like JavaScript objects, which can implement Protocols. That said, they are immutable, and don't support inheritance, preferring composition over it.
;; All objects are interface based, extendable and immutable.
;; Notice how the interface is at the forefront, and is defined first
(defprotocol APerson
(age-by [this years])
(change-name [this new-name]))
;; The class is defined second
(defrecord Person
[name age] ; The object fields
APerson ; Define that Person implements the methods of APerson
(age-by [this years] ; The first method implementation
(assoc this :age (+ age years))) ; Adds years to the age of the object under this
(change-name [this new-name] ; The second method implementation
(assoc this :name new-name))) ; Replaces name of the object by new-name.
;; Create a new instance of Person
(def john (->Person "John" 22))
;;=> #cljs.user.Person{:name "John", :age 22}
;; Call methods on it
(age-by john 10)
;;=> #cljs.user.Person{:name "John", :age 32}
;; But everything is immutable, including objects
(:age john)
;;=> 22
Large standard library
ClojureScript comes bundled with more functions and objects than normal JavaScript. It always includes the vast set of functions and objects from the ClojureScript standard library, as well as the Google Closure library.
That's right, ClojureScript always includes the Google Closure library, making it an integral part of ClojureScript. Whenever you can't find what you want in the ClojureScript standard lib, look to the Google Closure library instead.
The Google Closure library is similar to jQuery and core.js, it includes a huge swat of functions to manipulate the DOM, for server communication, animation, data-structures, unit testing, text editing and more. It also comes with UI widgets and controls. It is used by Google in all of their web products such as Gmail, Search, Maps, Docs, Photos, etc. Its robust, highly optimized, complete, well tested, and it also will minimize itself to end up taking the least amount of space possible, making page load fast. Oh, and it does all that in a way that works across all browsers.
Yup, that means with ClojureScript, you don't need jQuery, or core.js, or babel, or UglifyJS2, etc. Since it's all handled through its first class support for the Google Closure compiler.
Multiline and template strings
Strings can span multiple lines, which will result in newline characters being part of the string exactly where a new line was added when defining the string in code. Templates allow you to interpolate a string with variables, for safer string creation from user input.
;;; A multiline string
"This string spans
multiple lines."
;;=> "This string spans\nmultiple lines."
;;; Template strings
(let [first-name "John"
last-name "White"]
(gstr/format "Hello %s, %s!" first-name last-name))
;;=> "Hello John, White!"
Destructuring
Destructuring allows binding using pattern matching. It supports matching for all ClojureScript data structures and records. Failed destructuring does not throw, instead what was not matched is set to nil. See Type extension for a way to extend ClojureScript so that JavaScript objects can also be destructured.
;;; List matching
(let [[a _ b] [1 2 3]]
(println a) ;=> 1
(println b)) ;=> 3
;;; Object matching
(let [{:keys [name age]} john]
(println name) ;=> "John"
(println age)) ;=> 22
;;; Associative Map matching
(let [{:keys [a b]} {:a 10, :b 20}]
(println a) ;=> 10
(println b)) ;=> 20
;;; Can be used in parameter position
(defn g[{:keys [name]}]
(println name))
(g {:name "Marie"})
;;; Fail-soft destructuring
(let [[a] []]
(println a)) ;=> nil
;;; Default values in associative destructuring
(let [{:keys [a] :or {a 10}} {}]
(println a)) ;=> 10
Rest, Named, Spread and Overloaded arguments
Rest arguments lets you take a variable number of arguments. Named arguments allow you to pass arguments in any order. Spread arguments allow you to pass a sequence (list, vector, etc.) of elements as the arguments to a function. Finally, overloaded arguments allows to dispatch on a different implementation based on the number of arguments.
;;; Rest arguments
(defn f[x & rest]
;; rest is a sequence
(* x (count rest))) ; Multiplies x by the length of values in rest
(f 3 "hello" true)
;;=> 6
;;; Named arguments
(defn f[& {:keys [x y]}]
(- x y))
(f :y 5 :x 10)
;;=> 5
(f :x 10 :y 5)
;;=> 5
;;; Named arguments with default values
(defn f[& {:keys [x y] :or {x 100}}]
(- x y))
(f :y 5)
;;=> 95
;;; Spread function calls
(defn f[x y z]
(+ x y z))
;; Pass each element of sequence as argument
(apply f [1 2 3])
;;=> 6
;;; Overloaded function based on number of args
(defn f
([a b] (+ a b))
([a b c] (- a b c)))
(f 10 10) ;=> 20
(f 10 10 10) ;=> -10
Proper scopes
ClojureScript has proper scope, as everything is blocked scoped, supports nesting, and it all works as you'd expect. Inner scopes can see outer scopes, and they can also shadow outer scopes so names can be re-used. While outer scopes can not access inner scopes.
;; Define a global scope variable x
(def x -50)
;; Define a global scope function f
(defn f[x] ;; The argument x is defined in function scope
(let [x 100] ;; Define a block scope variable x
(let [x 0] ;; Nested block scope variable x
(println x))
(println x))
(println x)
(println cljs-features-demo/x))
(f 1)
;;=> 0
;;=> 100
;;=> 1
;;=> -50
;; Everything is always properly scoped.
;; `let` is also always immutable, so its similar to `const` in JS
;; Must wrap it in mutable container to get mutable variable
(let [a (atom 0)]
(println @a)
(reset! a 100)
(println @a))
;;=> 0
;;=> 100
Iterators and Lazy Sequences
ClojureScript supports iterators, much like Python, Java, and ES6. Its iterators behave more like Generators in their ease of use. The difference being that ClojureScript iterators are immutable, thus carried over data must be passed along in a recursive style.
Lazy sequences allows computation to run only when needed. Pipelines can be created over collections, like in C#'s LINQ, or Java 8 streams. Making data manipulation a trivial task.
;;; Iterators
(defn fibonacci[]
;; Create an iterator that generates the next fib
;; and carries over the last fib to be used by the
;; next generation.
(->> (iterate (fn [[a b]] [b (+ a b)]) [0 1])
;; Return the first element of all generated values
;; to drop all carried state used only for the next
;; generation
(map first)))
(take 5 (fibonacci)) ; Return the first 5 generation of fib
;;=> (0 1 1 2 3)
;; Similar to Generators in Python, except they are immutable
;;; Lazy sequences
(->> (fibonacci)
(take 10)
(map inc)
(remove even?))
;;=> (1 3 9 35)
;; Similar to C# LINQ expressions
Unicode support
ClojureScript supports unicode to an equal extent that the JavaScript target does.
;;; Single byte character syntax
(println \u263A)
;;=> ☺
;;; Code points aware count
(= 2 (count "𠮷"))
;;=> true
;;; Can define multibyte unicode chars
(= "\uD842\uDFB7" "𠮷")
;;=> true
;;; Can iterate over code points
(for [code-point (seq "\uD842\uDFB7")]
(println code-point))
;;=> �
;;=> �
Immutable data structures
If you hadn't clued-in yet, almost everything in ClojureScript is immutable, that includes its data structures. The trick is that, they're also extremely fast, and consume a minimal amount of memory. Most of them also come with really convenient literate syntax to create them.
;;; Persistent list
'(1 2 3)
;;=> (1 2 3)
;; Conj (add in Clojure parlance) to the front
(conj '(1 2 3) 4)
;;=> (4 1 2 3)
;; Peek from the front
(peek '(1 2 3))
;;=> 1
;;; Persistent vector
[1 2 3]
;;=> [1 2 3]
;; Conj to the back
(conj [1 2 3] 4)
;;=> [1 2 3 4]
;; Peek from the back
(peek [1 2 3])
;;=> 3
;;; Persistent queue
#queue[1 2 3]
;;=> (1 2 3)
;; Conj to the back
(conj #queue[1 2 3] 4)
;;=> (1 2 3 4)
;; Peek from the front
(peek #queue[1 2 3])
;;=> 1
;;; Persistent hash Set
#{3 2 1}
;;=> #{3 2 1}
;;; Persistent sorted set
(sorted-set 3 2 1)
;;=> #{1 2 3}
;;; Persistent hash map
{:b 2 :a 1}
;;=> {:b 2, :a 1}
;;; Persistent sorted map
(sorted-map :b 2 :a 1)
;;=> {:a 1, :b 2}
Mutable data structures
ClojureScript also comes with mutable data structures, but their use is discouraged to only when performance demands it. That's why it simply delegates to the host's JavaScript mutable data structures. Feel free to use all ES6 mutable data-structures, since ClojureScript will polyfill them to older JavaScript version through its use of the Google Closure compiler. You can also use the mutable data structures in Google Closure lib, such as AvlTree, Heap, Pool, Queue, Trie, etc.
You'll never be short on data structures in ClojureScript.
;; Simply re-uses the ones offered by JavaScript
;;; JavaScript Object
#js{:a 1 :b 2}
;;=> #js {:a 1, :b 2}
;; Read object properties by prepending `.-` to their name
(.-a #js{:a 1 :b 2})
;;=> 1
;; Can mutate using `set!`
(def my-obj #js{:a 1 :b 2})
my-obj
;;=> #js {:a 1, :b 2}
(set! (.-a my-obj) "Mutated!")
my-obj
;;=> #js {:a "Mutated!", :b 2}
;;; Arrays
(make-array 3)
;;=> #js [nil nil nil]
#js["first-array-element" "second-array-element"]
;;=> #js ["first-array-element" "second-array-element"]
;; Can mutate using `aset`
(def my-array #js[1 2 3])
my-array
;;=> #js [1 2 3]
(aset my-array 1 "Mutated!")
my-array
;;=> #js [1 "Mutated!" 3]
;; Can access indexed value using aget
(aget my-array 1)
;;=> "Mutated!"
;;; Map
(js/Map.)
;;=> #object[Map [object Map]]
;; Can set and get using host interop
(def my-mutable-map (js/Map.))
(.set my-mutable-map "a" 1)
(.get my-mutable-map "a")
;;=> 1
;;; Set
(js/Set.)
;;=> #object[Set [object Set]]
;; Can add and has using host interop
(def my-mutable-set (js/Set.))
(-> my-mutable-set (.add 1) (.add 2))
(.has my-mutable-set 2)
;;=> true
Controlled mutable values
Every value container that is not from the Google Closure library, or the host JavaScript, or third party library that you have imported yourself is immutable in ClojureScript, except for Atoms. An Atom allows very explicit use of a mutable value, and supports adding validation on change, as well as watches events that trigger on mutation of the value. Letting you put the proper control in place to avoid the mutability to cause bugs in your code.
;;; Mutable values with `atom`
(def a-mutable-val (atom nil))
@a-mutable-val ; Get the current value with `@` prefix
;;=> nil
(reset! a-mutable-val "Not nil!")
@a-mutable-val
;;=> "Not nil!"
;; Atoms are the only mutable value objects, apart for JavaScript host
;; data-structures/objects seen in the previous section.
;;; Watches, trigger event callback every time value changes
(add-watch a-mutable-val
:print-change-details
(fn[key a old-val new-val]
(println (gstr/format "Atom changed from %s to %s"
old-val new-val))))
(reset! a-mutable-val "I damn well changed its value again!")
;;=> Atom changed from Not nil! to I damn well changed its value again!
;;; Validators, validate new values
(set-validator! a-mutable-val
(fn[new-val]
(not= 0 new-val)))
(try
(reset! a-mutable-val 0)
(catch js/Error e
(println e)))
;;=> #object[Error Error: Validator rejected reference state]
Type extension
Don't think ClojureScript has enough features out of the box for you? Well, you are in luck, because it gives you the ability to easily extend any type, be they ClojureScript or host JavaScript, to your will, quite easily and simply.
;; You can extend any ClojureScript and JavaScript object at any time
;; Lets add a reverse method to JavaScript Strings
;; First we declare the protocol, remember, interface first!
(defprotocol Reversable
(reverse [this]))
;; Now we implement it for JavaScript strings
(extend-type string
Reversable
(reverse [this]
(-> this (.split "") (.reverse) (.join ""))))
(reverse "cool")
;;=> "looc"
;; Lets extend all JavaScript objects so they can be destructured
(extend-type object
ILookup
(-lookup
([this key]
(goog.object/get this (name key)))
([this key not-found]
(goog.object/get this (name key) not-found))))
(let [{:keys [a b]} #js{:a 10 :b 20}]
(+ a b))
;;=> 30
Thanks to Mike Fikes for the destructuring extension.
Syntax extension
It's also possible to extend the ClojureScript syntax by using macros and tagged literals. With macros, completely new syntax and semantics can be created, and called upon by the invocation of the macro. You don't need to wait for the next version of ClojureScript to add any missing language feature, but be careful, macros are for the advanced users, a powerful weapon, that can easily be misused, so avoid it unless absolutely necessary. Tagged literals are simple ways to add new literal notations to construct objects or data-structures.
;;; Macros
;; Wish you could use infix notation for binary functions?
(defmacro infix
[& code]
`(~(second code)
~(first code)
~(last code)))
(infix 10 + 20)
;;=>30
;;; Tagged literals
;; Wish there was a way to create JavaScript mutable Maps with literal syntax?
;; In a data_readers.cljc file, you add custom literal tags
{mut/map custom-tags/make-js-map}
;; Which calls the function associated with it at read time
(ns custom-tags)
(defn make-js-map
[m]
(js/Map.
(clj->js (into [] m))))
;; Letting you then use a literal syntax to create anything
(let [js-map #mut/map{:a 1 :b 2}]
(println (.get js-map "a")))
;;=> 1
Many more
ClojureScript supports many more features than the ones I highlighted here. It's a pretty complete language, you probably won't find anything missing, and if you do, the extension capabilities will allow you to add it yourself, without waiting for a new version to come out.
So consider this list incomplete, but here's some of the added features I didn't cover:
- Number literals: decimal, exponent, binary, octal, arbitrary base, etc.
- Regex literal
- EDN: JSON for ClojureScript
- shebang support
- Block comments
- Default arguments
- Asynchronous programming using Communicating sequential processes, same as in GO.
- Other literals: uuid, instant, etc.
- Metadata: Data about your data
- Reader conditionals: Allows code to work simultaneously with Clojure and ClojureScript
- Tail Call optimized recursion
- Custom types
- React support
- Spec: Declarative Data Specification with auto validation and generation. Can be used for generative testing, and powerful runtime validation
- Transients: Mutable escape hatch for immutable data structures when performance is required.
- Transducers: Efficient and composable data transformation
- Zippers: Navigational iteration through Tree and Graph data structures
- Output source minification
- Output source dead code elimination
- Polyfill of ECMAScript 6+ to ECMAScript 5 or 3, so you can use newer APIs in older JavaScript environemnts
- And so much more...
Tell us about the compiler. How friendly is it? How fast? Do I have multiple compilers to choose from?
ReplyDeleteThere's only one compiler. It is implemented in portable Clojure. That means it can be run on the JVM as Clojure, or on JavaScript as ClojureScript.
DeleteI don't have benchmarks, but I can tell you it's pretty fast. I've never found myself waiting after it. It is fast enough that the normal workflow is to have it live recompile on file changes and thus you can see your changes in the browser in pseudo real time. There's even tooling that makes it you don't even have to refresh the page to see the changes.
If you run it on the JVM, it compiles faster, but starts slower. If you run it on JavaScript, like Node, it compiles slower, but starts faster.
That means the compiler can compile itself.
I'm not sure what you're looking for in terms of friendliness?
There's a command line interface to it. You'll have to learn what command it takes.
Or it exposes a portable Clojure API, so you can use it as a library from inside Clojure or ClojureScript to have it compile code.
There are multiple build tools available to help you with it. Shadow-cljs, Leiningen, Boot, Lumo and Calvin are the popular ones.
I'll try and write a follow up article, a quick start where I explain how you can get started and try out some of those features for yourself.
Hope this answered some of your questions, feel free to ask more.
Thanks for reading.