ClojureScript
Differences from Clojure

Differences from Clojure

Rationale

The rationale for ClojureScript is much the same as for Clojure, with JavaScript in the role of platform, and additional emphasis on the reach of JS, as it is obviously not as rich a platform.

A deeper discussion on ClojureScript’s rationale can be found elsewhere on this site.

State and Identity

Same as Clojure. Clojure’s identity model is simpler and more robust than mutable state, even in single threaded environments.

Dynamic Development

As with Clojure, ClojureScript supports REPL-driven development, providing easily-launched REPLs for various JavaScript environments. See Quick Start for details.

Additionally, ClojureScript’s self-hosting capability supports extending the dynamic nature to pure JavaScript environments where third-party REPLs and other dynamic facilities can be created.

Functional Programming

ClojureScript has the same immutable persistent collections as Clojure on the JVM.

Lisp

Unlike in Clojure, ClojureScript macro definitions and their use cannot be intermixed in the same compilation stage. See the Macros section below.

Runtime Polymorphism

  • ClojureScript protocols have the same semantics as Clojure protocols.

Concurrent Programming

Clojure’s model of values, state, identity, and time is valuable even in single-threaded environments.

  • Atoms work as in Clojure

  • No Refs nor STM

  • The user experience of binding is similar to that in Clojure

    • Vars

      • not reified at runtime

      • many development time uses of reification are obviated by access to Clojure data structures via the analyzer

    • def produces ordinary JS variables

  • Agents are currently not implemented

Hosted on the JVM

Getting Started

The Reader

  • Numbers

    • ClojureScript currently only supports integer and floating point literals that map to JavaScript primitives

      • Ratio, BigDecimal, and BigInteger literals are currently not supported

      • Equality on numbers works like JavaScript, not Clojure: (= 0.0 0) ⇒ true

  • Characters

    • ClojureScript does not have character literals. Instead characters are the same as in JavaScript (i.e. single-character strings)

  • Lists, Vectors, Maps, and Set literals are the same as in Clojure

  • Macro characters

    • Because there is no character type in ClojureScript, \ produces a single-character string.

  • read

    • The read and read-string functions are located in the cljs.reader namespace

  • Tagged Literals

    • The same as Clojure Tagged Literals, except that reader functions used at the compilation stage are similar to a Clojurescript macros, in that they should return a Clojurescript code form (or a literal such as a string or number).

    • The Clojure compiler does not automatically require reader functions referred to in data_readers.clj/c, but the Clojurescript compiler does.

    • see The Reader for more details

The REPL and main

  • See the Quick Start for ClojureScript REPL usage.

  • The standard ClojureScript REPLs support the Clojure main pattern.

Evaluation

  • ClojureScript has the same evaluation rules as Clojure

  • load exists, but only as a REPL special function

  • load-file exists, but only as a REPL special function

  • While Clojure performs locals clearing, ClojureScript does not

Special Forms

The following ClojureScript special forms are identical to their Clojure cousins: if, do, let, letfn, quote, loop, recur, throw, and try.

  • var notes

    • Vars are not reified at runtime. When the compiler encounters the var special form it emits a Var instance reflecting compile time metadata. (This satisfies many common static use cases.)

  • def notes

    • def produces ordinary JS variables

    • :private metadata is not enforced by the compiler

      • Private var access triggers an analysis warning

    • :const metadata:

      • will cause inlining of compile-time static EDN values

      • causes case test constants which are symbols resolving to ^:const Vars to be inlined with their values

    • A def form evaluates to the value of the init form (instead of the var), unless the :def-emits-var compiler option is set (which defaults to true for REPLs)

  • if notes

    • the section about Java’s boolean boxes is irrelevant in ClojureScript

  • fn notes

    • There is currently no runtime enforcement of arity when calling a fn

  • monitor-enter, monitor-exit, and locking are not implemented

Macros

ClojureScript’s macros must be defined in a different compilation stage than the one from where they are consumed. One way to achieve this is to define them in one namespace and use them from another.

Macros are referenced via the :require-macros keyword in namespace declarations:

(ns my.namespace
  (:require-macros [my.macros :as my]))

Sugared and other ns variants can be employed in lieu of using the :require-macros primitive; see Namespaces below for details.

Macros are written in *.clj or *.cljc files and are compiled either as Clojure when using regular ClojureScript or as ClojureScript when using bootstrapped / self-host ClojureScript. One point of note is that the code generated by Clojure-based ClojureScript macros must target the capabilities in ClojureScript.

ClojureScript namespaces can require macros from the selfsame namespace, so long as they are kept in different compilation stages. So, for example a foo.cljs or foo.cljc file can make use of a foo.cljc or foo.clj file for its macros.

Unlike in Clojure, in ClojureScript a macro and a function can have the same name (for example the cljs.core/+ macro and cljs.core/+ function can coexist).

You may be wondering: “If that’s the case, which one do I get?” ClojureScript (unlike Clojure) has two distinct stages that make use of two separate non-interacting namespaces. Macroexpansion occurs first, so a form like (+ 1 1) initially involves the cljs.core/+ macro. On the other hand, in a form like (reduce + [1 1]), the + symbol is not in operator position, and passes untouched through macroexpansion to analysis/compilation where it is resolved as the cljs.core/+ function.

Other Functions

  • printing

    • *out* and *err* is currently not implemented

  • regex support

  • asserts

    • In JVM ClojureScript it is not possible to dynamically set *assert* to false at runtime. Instead the :elide-asserts compiler option must be used to effect elision. (On the other hand, in self-hosted ClojureScript *assert* behaves identically to Clojure.)

Data Structures

  • nil

    • While in Clojure, nil is identical to Java’s null, in ClojureScript nil is equivalent to JavaScript’s null and undefined.

  • Numbers

    • Currently ClojureScript numbers are just JavaScript numbers

  • Coercions are not implemented, since there are currently no types to coerce to

  • Characters

    • JavaScript has no character type. Clojure characters are represented internally as single-character strings

  • Keywords

    • ClojureScript keywords are not guaranteed to be identical?, for fast equality testing use keyword-identical?

  • Collections

    • Persistent collections available

      • Ports of Clojure’s implementations

    • Transient support in place for persistent vectors, hash maps and hash sets

    • Most but not all collection fns are implemented

  • StructMaps

    • ClojureScript does not implement defstruct, create-struct, struct-map, struct, or accessor.

Seqs

  • Seqs have the same semantics as in Clojure, and almost all Seq library functions are available in ClojureScript.

Protocols

  • defprotocol and deftype, extend-type, extend-protocol work as in Clojure

  • Protocols are not reified as in Clojure, there are no runtime protocol objects

  • Some reflective capabilities (satisfies?) work as in Clojure

    • satisfies? is a macro and must be passed a protocol name

  • extend is not currently implemented

  • specify, extend immutable values to protocols - instance level extend-type without wrappers

Metadata

Works as in Clojure.

Namespaces

Namespaces in ClojureScript are compiled to Google Closure namespaces which are represented as nested JavaScript objects. Importantly this means that namespaces and vars have the potential to clash - however the compiler can detect these problematic cases and will emit a warning when this occurs.

  • You must currently use the ns form only with the following caveats

    • You must use the :only form of :use

    • :require supports :as, :refer, and :rename

      • :refer :all not supported

      • all options can be skipped

      • in this case a symbol can be used as a libspec directly

        • that is, (:require lib.foo) and (:require [lib.foo]) are both supported and mean the same thing

      • :rename specifies a map from referred var names to different symbols (and can be used to prevent clashes)

      • prefix lists are not supported

    • The only options for :refer-clojure are :exclude and :rename

    • :import is available only for importing Google Closure classes

      • ClojureScript types and records should be brought in with :use or :require :refer, not :import ed

  • Macros must be defined in a different compilation stage than the one from where they are consumed. One way to achieve this is to define them in one namespace and use them from another. They are referenced via the :require-macros / :use-macros options to ns

    • :require-macros and :use-macros support the same forms that :require and :use do

Implicit macro loading: If a namespace is required or used, and that namespace itself requires or uses macros from its own namespace, then the macros will be implicitly required or used using the same specifications. Furthermore, in this case, macro vars may be included in a :refer or :only spec. This oftentimes leads to simplified library usage, such that the consuming namespace need not be concerned about explicitly distinguishing between whether certain vars are functions or macros. For example:

(ns testme.core (:require [cljs.test :as test :refer [test-var deftest]]))

will result in test/is resolving properly, along with the test-var function and the deftest macro being available unqualified.

Inline macro specification: As a convenience, :require can be given either :include-macros true or :refer-macros [syms…​]. Both desugar into forms which explicitly load the matching Clojure file containing macros. (This works independently of whether the namespace being required internally requires or uses its own macros.) For example:

(ns testme.core
  (:require [foo.core :as foo :refer [foo-fn] :include-macros true]
            [woz.core :as woz :refer [woz-fn] :refer-macros [apple jax]]))

is sugar for

(ns testme.core
  (:require [foo.core :as foo :refer [foo-fn]]
            [woz.core :as woz :refer [woz-fn]])
  (:require-macros [foo.core :as foo]
                   [woz.core :as woz :refer [apple jax]]))

Auto-aliasing clojure namespaces: If a non-existing clojure.* namespace is required or used and a matching cljs.* namespace exists, the cljs.* namespace will be loaded and an alias will be automatically established from the clojure.* namespace to the cljs.* namespace. For example:

(ns testme.core (:require [clojure.test]))

will be automatically converted to

(ns testme.core (:require [cljs.test :as clojure.test]))

Libs

Existing Clojure libs will have to conform to the ClojureScript subset in order to work in ClojureScript.

Additionally, macros in Clojure libs must be compilable as ClojureScript in order to be consumable in self-host / bootstrapped ClojureScript via its cljs.js/*load-fn* capability.

Vars and the Global Environment

  • def and binding work as in Clojure

    • but on ordinary js variables

    • Clojure can represent unbound vars. In ClojureScript (def x) results in (nil? x) being true. (In this case, (identical? nil x) is false, but (identical? js/undefined x) is true.)

    • In Clojure, def yields the var itself. In ClojureScript def yields the value, unless the REPL option :def-emits-var is set (this defaults to true for REPLs).

  • Atoms work as in Clojure

  • Refs and Agents are not currently implemented

  • Validators work as in Clojure

  • intern not implemented - no reified Vars

Refs and Transactions

Refs and transactions are not currently supported.

Agents

Agents are not currently supported.

Atoms

Atoms work as in Clojure.

Host Interop

The host language interop features (new, /, ., etc.) work as in Clojure where possible, e.g.:

goog/LOCALE
=> "en"

(let [sb (goog.string.StringBuffer. "hello, ")]
 (.append sb "world")
 (.toString sb))
    => "hello, world"

In ClojureScript Foo/bar always means that Foo is a namespace. It cannot be used for the Java static field access pattern common in Clojure as there’s no reflection information in JavaScript to determine this.

The special namespace js provides access to global properties:

js/Infinity
=> Infinity

To access object properties (including functions that you want as a value, rather than to execute) use a leading hyphen:

(.-NEGATIVE_INFINITY js/Number)
=> -Infinity

Hinting

While ^long and ^double—when used on function parameters—are type declarations in Clojure, they are type hints in ClojureScript.

Type hinting is primarily used to avoid reflection in Clojure. In ClojureScript, the only type hint of significance is the ^boolean type hint: It is used to avoid checked if evaluation (which copes with the fact that, for example, 0 and "" are false in JavaScript and true in ClojureScript).

Compilation and Class Generation

Compilation is different from Clojure:

  • All ClojureScript programs are compiled into (optionally optimized) JavaScript.

  • Individual files can be compiled into individual JS files for analysis of output

  • Production compilation is whole-program compilation via Google Closure compiler

  • gen-class, gen-interface, etc. are unnecessary and unimplemented in ClojureScript

Other Libraries

ClojureScript currently includes the following non-core namespaces ported from Clojure:

  • clojure.set

  • clojure.string

  • clojure.walk

  • clojure.zip

  • clojure.data

  • clojure.core.reducers

    • fold is currently an alias for reduce

  • cljs.pprint (port of clojure.pprint)

  • cljs.spec (port of clojure.spec)

  • cljs.test (port of clojure.test)

Contributing

Clojure and ClojureScript share the same Contributor Agreement and development process.