There are several Clojure implementations (Clojure, ClojureScript, ClojureCLR). The problem we wish to solve is how to write a single source file that runs on more than one of these platform implementations, retaining all of the common code and factoring out only the platform-specific bits.
Known use cases for platform-specific functionality:
There are other potential uses for conditionally including code based on factors other than platform (host architecture or presence of some external capability).
One approach is to maintain two versions of the same file that are largely the same but modify the platform-specific parts in each copy. This obviously works but is gross.
cljx is an implementation of feature expressions that:
.cljxfiles containing feature expression-annotated code into external files based on well-known tags
.cljsfiles for consumption by other tools/compilers/etc
It has been used successfully by a number of projects (see the cljx README for a partial list). cljx's limitations include:
#+clj) and one for ClojureScript (
#+cljs). Further discrimination based on target runtime (e.g. rhino vs. node vs. v8 vs. CLR) would be trivial, but has not been implemented to date.
cljx expressions are typically applied:
lein-cljsbuild provides a (deprecated, to be removed) feature called "crossovers" that provides a very limited preprocessing of certain files during the cljsbuild build process; a special comment string is removed, allowing one to work around the
-macros declarations required in ClojureScript
ns forms. Crossover files must otherwise be fully portable. Language/runtime-specific code must be maintained in separate files. However, (my) experience shows that this can quickly lead to the situation where one has to think a lot about in which file to put a specific function, in order to go though the whole preprocessing machinery. Functions are split into namespaces because of conditional compilation, and not because they belong to the same part or module of the program.
Define a custom tagged literal that implements conditional read-time expressions:
#feature/condf [ (and jdk-1.6+ clj-1.5.*)
else (some-old-fashioned-code) ]
Proof of concept here: https://github.com/miner/wilkins
A solution to this problem that is used by Common Lisp implementations are feature expressions. Each platform has a variable called
*features* that contains keywords that indicate the supported features of the platform the code is running under. The branching on a platform or a platform specific feature is done via the reader macros
#- followed by a feature condition. The feature condition is either a symbol or a form that combines symbols with the
not operators. The feature condition is evaluated by looking up the symbols in the
*features* variable. If the feature condition evaluates to true the next form will be passed through the reader and evaluated, otherwise it will be discarded.
The patches attached to the CLJS-27 ticket contain a proof of concept implementation of these feature expressions for Clojure and ClojureScript. With this extension one can branch on a form level and write code like illustrated by the following example:
(ns feature.expressions #+cljs (:require [goog.string :as gstring])) (defn my-trim [s] #+clj (.. s toString trim) #+cljs (gstring/trim s)) (my-trim " Hello CL? ")
The patches add a dynamic variable called
*features* to the clojure.core and cljs.core namespaces, that should contain the supported features of the platform in question as keywords. Unlike in Common Lisp, the variable is a Clojure set and not a list. In Clojure the set contains the :clj keyword, and in ClojureScript the :cljs keyword.
I would like to get feedback on the following issues:
*.cljfiles? What happens to
To run the ClojureScript tests, drop a JAR named "clojure.jar" that has the Clojure patch applied into ClojureScript's lib directory.
The Common Lisp Hyperspec about the Sharp Sign macros:
Examples of Common Lisp's Feature Expresions:
Maintaining Portable Lisp Programs:
Crossover files in lein-cljsbuild:
ClojureScript JIRA Tickets and patches with a proof of concept implementation of CL's feature expressions: