Dunaj - Deconstructing Core API
While Clojure provides its functionalities in multiple namespaces
clojure.zip), the majority of it
is defined in a single namespace called
The first Dunaj experiment explores the idea of having multiple
small namespaces where functions, macros and other public vars
are grouped by their purpose. It investigates whether such separation
is possible at all and whether it can be practical and useful.
It will be interesting to see whether this experiment will be also
able to lower the learning curve for beginners and improve the ease
of use and clarity of the Clojure API.
The goals of the first Dunaj experiment are as follows:
Devise a new user centric core API comprising multiple namespaces, leaving bootstrapping and low level vars in the
Define a concept of API presets that control which functions, macros and vars gets referred by default.
Let user choose which API preset he/she wants to use in his/hers namespace, using classic
clojure.coreas a default.
The upside of this approach is that backwards compatibility is maintained and users can freely intermix multiple APIs in their projects. Functionalities can be more logically separated by their purpose. List of automatically referred vars is no longer driven by the namespace in which vars were defined, but this list is handled by a separate API preset that can be extended and customized.
Dunaj API and SPI
Dunaj takes functionalities found in
clojure.core and in 9
other clojure namespaces (such as
clojure.walk) and divide them into more than 50 namespaces,
grouping vars by their purpose. Moreover, distinction between
API (functions, macros) and SPI (protocols,
protocol methods) has been made explicit in the documentation that
comes with Dunaj.
Example of Dunaj namespaces together with some of their vars:
Vars named by following symbols are the only ones that a have different meaning in Dunaj and Clojure:
API presets enable developers to elegantly switch between Dunaj and
Clojure within the same project.
ns macro in Clojure has been
patched to support an additional
:api declaration that states which
API preset should be used in the respective namespace. Dunaj provides
three built-in API presets:
clojure- Refers all vars from
clojure.core, plus Clojure’s special symbols and a default set of host classes. This preset is used by default when no preset is specified in the
bare- Does not refer any vars or host classes. Refers special symbols.
dunaj- Loads Dunaj and refers less than 600 most commonly used vars from Dunaj (out of more than 1700). Refers special symbols too. No vars from
|API Presets functionality is not available in Dunaj lite. Please consult Dunaj lite documentation for a way how to work around this limitation.|
Custom API presets can be easily created and used in the same way as the three built-in presets. Following examples shows how API presets are used.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ;; classic Clojure API (ns foo.bar) (time (reduce + (take 100000 (range)))) ;; Elapsed time: 42.034772 msecs (Clojure 1.7 alpha5) ;;=> 4999950000 (def a (atom 0)) ;;=> #'foo.bar/a (add-watch a :foo (fn [r k o n] (println "state changed to" n))) ;;=> #<Atom@3560ac26: 0> (set-validator! a even?) ;;=> nil (swap! a inc) ;; java.lang.IllegalStateException: Invalid reference state (swap! a #(+ 2 %)) ;; state changed to 2 ;;=> 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ;; Dunaj API. Note differences in elapsed time, var names and idioms (ns foo.bar (:api dunaj) (:require [dunaj.concurrent.port :refer [reduce!]])) (time (reduce + (take 100000 (range)))) ;; Elapsed time: 15.84147 msecs ;;=> 4999950000 (def a (atom 0)) ;;=> #'foo.bar/a (def c (chan)) ;;=> #'foo.bar/c (tap! a c) ;;=> #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@6203bf4d> (reduce! (fn [_ [_ o n]] (println! "state changed to" n)) nil c) ;;=> #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@26602ed2> (reset! (validator a) even?) ;;=> #<core$even_QMARK_ clojure.core$even_QMARK_@7ea7141a> (alter! a inc) ;; java.lang.IllegalStateException: Invalid reference state (alter! a #(+ 2 %)) ;; state changed to 2 ;;=> 2
1 2 3 4 5 6 7 8 ;; Dunaj API with exclusions (ns foo.bar (:api dunaj :exclude [+]) (:require [dunaj.math.precise :refer [+]])) ;; + now supports arbitrary precision (+ 9223372036854775800 10) ;;=> 9223372036854775810N
1 2 3 4 5 6 7 8 9 ;; Bare API, no symbols refered, not even host classes (ns foo.bar (:api bare)) (+ 1 2) ;; java.lang.RuntimeException: Unable to resolve symbol: + in this context String ;; java.lang.RuntimeException: Unable to resolve symbol: String in this context