-
Notifications
You must be signed in to change notification settings - Fork 792
Differences from Clojure
What follows is a section-by-section review of the sections on the left-hand navigation panel of http://clojure.org, enumerating what is different in ClojureScript and in some cases the similarities.
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.
Same as Clojure. Clojure's identity model is simpler and more robust than mutable state, even in single threaded environments.
ClojureScript's REPL can be launched from within the Clojure REPL. 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.
ClojureScript has the same immutable persistent collections as Clojure on the JVM.
Unlike in Clojure, ClojureScript macro definitions and their use cannot be intermixed in the same compilation stage. See the Macros section below.
- ClojureScript protocols have the same semantics as Clojure protocols.
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
bindingis 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
-
defproduces ordinary JS variables
- Vars
- Agents are currently not implemented
- ClojureScript is hosted on JavaScript VMs
- Optionally, it may use Google's Closure compiler for optimization
- It is designed to leverage Google's Closure library, and participates in its dependency/require/provide mechanism
See Quick Start
-
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 has javascript (not clojure's) semantic: (= 0.0 0) => true
- ClojureScript currently only supports integer and floating point literals that map to JavaScript primitives
-
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.
- Because there is no character type in ClojureScript,
-
read- The
readandread-stringfunctions are located in thecljs.readernamespace
- The
- See Quick Start for instructions the ClojureScript REPL.
-
mainsupport is currently not implemented
- ClojureScript has the same evaluation rules as Clojure
-
loadis planned -
load-fileexists, but only as a REPL special function
The following ClojureScript special forms are identical to their Clojure
cousins: if, do, let, letfn, quote, loop, recur, throw, and try.
-
varnotes- Vars are not reified at runtime. When the compiler encounters the
varspecial form it emits aVarinstance reflecting compile time metadata. (This satisfies many common static use cases.)
- Vars are not reified at runtime. When the compiler encounters the
-
defnotes-
defproduces ordinary JS variables -
:privatemetadata is not enforced by the compiler -
:constmetadata in ClojureScript disallows re-defand supportscase. In Clojure:constallows re-defbut value inlined at use site. - A
defform evaluates to the value of the init form (instead of the var)
-
-
ifnotes- the section about Java's boolean boxes is irrelevant in ClojureScript
-
fnnotes- There is currently no runtime enforcement of arity when calling a fn
-
monitor-enter,monitor-exit, andlockingare not implemented
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.cljsorfoo.cljcfile can make use of afoo.cljcorfoo.cljfile 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).
Note: 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 thecljs.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 thecljs.core/+function.
- printing
-
*out*is currently not implemented
-
- regex support
- ClojureScript regular expression support is that of JavaScript
- asserts
- It is currently not possible to dynamically set
*assert*to false at runtime. Instead the:elide-assertscompiler option must be used to effect elision.
- It is currently not possible to dynamically set
- 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 usekeyword-identical?
- ClojureScript keywords are not guaranteed to be
- 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
- Persistent collections available
- Seqs have the same semantics as in Clojure, and almost all Seq library functions are available in ClojureScript.
-
defprotocolanddeftype,extend-type,extend-protocolwork 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
-
-
extendis not currently implemented -
specify, extend immutable values to protocols - instance levelextend-typewithout wrappers
Works as in Clojure.
- You must currently use the
nsform only with the following caveats- You must use the
:onlyform of:use -
:requiresupports:asand:refer- both 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
- that is,
- prefix lists are not supported
- The only option for
:refer-clojureis:exclude -
:importis available only for importing Google Closure classes- ClojureScript types and records should be brought in with
:useor:require :refer, not:imported
- ClojureScript types and records should be brought in with
- You must use the
- 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-macrosoptions tons-
:require-macrosand:use-macrossupport the same forms that:requireand:usedo
-
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. 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.
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]]))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.
-
defandbindingwork as in Clojure- but on ordinary js variables
- Clojure can represent unbound vars. In ClojureScript
(def x)results in(nil? x)being true. - In Clojure,
defyields the var itself. In ClojureScriptdefyields the value, unless the REPL option :def-emits-var is set (this defaults totruefor REPLs).
- Atoms work as in Clojure
- Refs and Agents are not currently implemented
- Validators work as in Clojure
-
internnot implemented - no reified Vars
Refs and transactions are not currently supported.
Agents are not currently supported.
Atoms work as in Clojure.
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.
To access object properties (including functions that you want as a value, rather than to execute) use a leading hyphen:
(.-Infinity js/window)
=> Infinity
While ^long and ^double—when used on function parameters—are type declarations in Clojure, they are type hints in ClojureScript.
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
ClojureScript currently includes the following non-core namespaces ported from Clojure:
clojure.setclojure.stringclojure.walkclojure.zipclojure.data-
clojure.core.reducers-
foldis currently an alias forreduce
-
-
cljs.pprint(port ofclojure.pprint) -
cljs.test(port ofclojure.test)
Clojure and ClojureScript share the same Contributor Agreement and development process.
- Rationale
- Quick Start
- Differences from Clojure
- [Usage of Google Closure](Google Closure)