(deftype Foo
SomeCommonProtocol
(aMethod [blah blah] ...))
(+clj
(extend-type Foo
JvmOnlyInterface
(someMethod [blah] ...)))
Can you come up with another example you consider intractable? (deftype Foo
SomeCommonProtocol
(aMethod [blah blah] ...))
(+clj
(extend-type Foo
JvmOnlyInterface
(someMethod [blah] ...)))
Can you come up with another example you consider intractable?That said, there are countless examples. I already mentioned the proposed ns+ form, but there are a couple more in the design spec here: http://dev.clojure.org/display/design/Feature+Expressions
I'm not going to bother constructing more concrete examples, since it's trivial to do and each and everyone will have case-specific solutions that will seem "nicer" than feature expressions. But that's just it: It will be a case-by-case solution, rather than a general solution. Case-by-case application-centric solutions are preferable for the platform abstractions of your major subsystems, but often you just want to hack it and feature-macros simply don't offer the flexibility you need.
(case-host
:clj
(deftype Foo []
JvmOnlyInterface
(aMethod [blah blah] ...))
:cljs
(deftype Foo []))
(extend-type Foo
SomeCommonProtocol
(someMethod [blah] ...))
It is true there are countless examples. There are also countless counter-examples. And for the examples that are competitive in terseness, there is already cljx.I personally don't "just want to hack it", especially when it comes to writing portable code. I agree that portability poses subtle new challenges to application design. I just prefer to meet those challenges in a way that is programmable, which syntactic extension is definitely not.
As for the programability complaint: I just don't see why it is important. If you are generating code, you can already add/remove content from the generated code. That is: you can always just write a "feature macro"! Feature expressions don't add any power you don't already have: Only affordances for humans.
Unrelated thought: Haskell/GHC uses the C preprocessor to address this problem.
What excites us most about Feature Macros is that they do add power. Here is an example of something we think is powerful from the proposal - a cross-platform macro:
(case-host
:cljs nil
:clj (defmacro my-macro [name & body]
(case-target
:clj `(.println (System/-out) (str (do ~@body)))
:cljs `(.log js/console (str (do ~@body))))))2) By "power" I meant a formal notion of expressiveness: Neither feature expressions nor feature macros add anything to the language that you can't already express with existing constructs. Like I said: It's about affordances, not capability.
#+clj
(defmacro my-macro []
(cond
(contains? *features* :clj) `(some-clojure-thing)
(contains? *features* :cljs) `(some-cljs-thing)))
If that was too verbose for you, you could of course write a `feature-case` function that looks almost identical to what you have in that example.Interestingly, in the course of concocting a counter-counter example, I came up with this in an effort to show the two dynamic variables we stipulate - host and target - were absolutely required:
#+clj
(defmacro my-macro []
(cond
(contains? *features* :clj) `(some-clojure-thing)
(contains? *features* :cljs) `~(do (require 'some-ns)
((resolve 'some-ns/some-fn)))))
My hope was to make the point that 'some-ns/some-fn would run in a
context where (features :cljs) is true - as it is in the caller's
(macro body) environment - which would result in the system attempting
to run ClojureScript code, which would explode because the macro is
Clojure. Then I realized that require runs load, and load is in the
macro body. The load/require available in the environment is
Clojure-specific! Thus, load/require can bind disj :cljs and conj
:clj before reading and compiling some-ns.My counter-example failed, but did bring me a little closer to the truth - that we only need one dynamic variable, platform. Any platform that has the ability to load code also has an opportunity to bind platform and thereby inform macros of target. This simplifies the Feature Macro proposal by half and we are excited to amend it.
Our updated cross-platform example becomes:
(case-platform
:cljs nil
:clj (defmacro my-macro []
(condp = *platform*
:clj `(some-clojure-thing)
:cljs `(some-cljs-thing))))
Instead of host and target, we are down to just platform. We
continue to see no need for a features set because of Clojure's
platform-symbiosis.