Clojurescript 1.10.516
has been released on January 31, 2019
This article is an interactive version of the official announcement on Clojurescript.org, with the permission of Alex Miller.
Noteworthy Changes
Spec instrumentation (cljs.spec.test.alpha/instrument
and related functionality) no longer requires test.check
.
Let’s define a function fooz
that receives numbers only:
(require '[cljs.spec.test.alpha :as stest])
(require '[cljs.spec.alpha :as s])
(defn fooz [x] x)
(s/fdef fooz :args (s/cat :x number?))
(stest/instrument `fooz)
Now, when we call fooz
with an argument that is not a number, spec
emits an error:
(fooz "42")
If you use cljs.spec.test.alpha/check
, the data generation functionality of test.check
is needed; in that case you need to require the clojure.test.check
namespace.
Keywords used in the cljs.spec.test.alpha/check
API pertaining to Spec’s use of test.check
are now qualified with clojure.spec.test.check
, thus aligning with Clojure. The previous way of qualifying with clojure.test.check
is still supported.
Clojure 1.10 Features
Improved Exception Messages and Printing
The improved exception infrastructure added in Clojure 1.10 has been ported to ClojureScript with this release.
Protocols via Metadata
The ability to add protocols via metadata (for protocols defined with the :extend-via-metadata true directive) has been ported to ClojureScript with this release.
Datafy and Nav
The clojure.datafy namespace has been ported to ClojureScript, along with associated protocols in the clojure.core.protocols namespace.
See Welcome Clojure 1.10 for interactive examples of improved exception messages, protocols via Metadata, Datafy and Nav.
clojure.edn namespace
A new clojure.edn
namespace is added with this release which delegates to cljs.reader
for functionality. This facilitates writing portable Clojure/ClojureScript source making use of clojure.edn/read
and clojure.edn/read-string
:
(require 'clojure.edn)
(clojure.edn/read-string "(def a)")
Type Inference Improvements
Predicate-Induced Type Inference
The type inference algorithm will now consider core predicates when inferring the types of locals used in conditional expressions.
For example, in
(if (string? x)
(inc x)
10)
because x
satisfies string?
, it will be inferred to be of string type in the then branch (and thus cause a warning to be emitted because inc is being applied to it).
(defn my-inc-if [x]
(if (string? x)
(inc x)
10))
Because cond
and when
are macros built on top of if
, predicate-induced inference also works as expected for expressions involving cond
and when
.
(defn my-inc-when [x]
(when (string? x)
(inc x)
10))
In addition to core predicates, predicate-induced type inference also works for instance?
checks. So, for example testing (instance? Atom x)
will result in x
being inferred of type cljs.core/Atom
.
Truthy-Induced Inference
In situations where a value could potentially be nil
(represented by the symbol clj-nil
in type tags), if a simple symbol referring to such a value is used as the test in a conditional, the type inference algorithm will infer that the value cannot be nil
in the then branch.
This is perhaps best illustrated by way of example. Let’s say you have the following function:
(defn f [x]
(when (even? x)
(inc x)))
This function’s return type is #{number clj-nil}
, meaning that either a number or nil
can be returned.
The following function, which uses f
and would previously be inferred as returning #{number clj-nil}
, is now inferred as returning number:
(defn g [y]
(let [z (f y)]
(if z
z
17)))
In fact, owing to the way the or macro expands, the expression (or (f 1) 17)
is now inferred as being simply number.
Improved loop / recur Inference
The type-inferrence algorithm will now consider recur
parameter types when inferring loop
local types.
For example, in
(loop [x "a"]
(if (= "a" x)
(recur 1)
(+ 3 x)))
the local x
would previously be inferred to be of string type (and this would cause a warning to be emitted for the expression adding it to 3). Now, the compiler will infer x
to be either string or numeric (and thus the warning will no longer appear).
Multi-Arity and Variadic Function Return Type Inference
ClojureScript 1.10.439
added function return type inference, but this capability only worked for single-arity functions. This release extends this capability to multi-arity and variadic functions.
Furthermore, the inferred return type will properly vary if different arities return different types. For example,
(defn foo
([x] 1)
([x y] "a"))
then the expression (foo true)
will be inferred to be of numeric type while (foo :a :b)
will be inferred to be of string type.
If we try to call inc
on (foo :a :b)
, we get a warning:
(inc (foo :a :b))
However, the warning is not emitted, when we call inc
on (foo true)
:
(inc (foo true))
Spec Improvements
Several improvements in the Spec implementation are in this release, making it easier to spec functions in the standard core library, as well as improving instrumentation performance when a large number of functions in a codebase have specs.
Improved Performance
Chunked-Seq support for Ranges
ClojureScript now supports chunked-seqs
for ranges. An example where this capability improves performance is
(reduce + (map inc (map inc (range (* 1024 1024)))))
which is evaluated 5 times faster in V8, 7 times in SpiderMonkey, and 2 times in JavaScriptCore.
Please uncomment the following code snippet, in order to check how much time it takes on your browser:
(comment (time (reduce + (map inc (map inc (range (* 1024 1024)))))))
Improved re-seq Performance
re-seq
performance has been improved, with a speedup of 1.5 or more under major JavaScript engines.
Optimized String Expression Concatenation
Generally, arguments supplied to the str
function are first coerced to strings before being concatenated. With this release, unnecessary coercion is eliminated for arguments that are inferred to be of string type, leading to more compact codegen as well as a speed boost.
For example, in
(defn foo [x y]
(str (+ x y)))
(str (name :foo/bar) "-" (foo 3 2))
the last str
expression is evaluated 3 times faster in V8 and 4 times faster in JavaSriptCore as a result of the improved codgen.
Here is how the transpiled code looks like:
(defn foo [x y]
(str (+ x y)))
(str (name :foo/bar) "-" (foo 3 2))
Change List
For a complete list of updates in ClojureScript 1.10.516 see Changes.
Contributors
Thanks to all of the community members who contributed to ClojureScript 1.10.516:
- Anton Fonarev
- Enzzo Cavallo
- Erik Assum
- Eugene Kostenko
- Martin Kučera
- Michiel Borkent
- Oliver Caldwell
- Sahil Kang
- Thomas Heller
- Thomas Mulvaney
- Timothy Pratley
- Will Acton