Many of the most useful applications of feature expressions simply will not work as macros. You'd need to create one new feature-cond enabled macro for each other macro use case. Feature expressions, on the other hand, are a general solution that can be used at the call site without having to reinvent every macro that may ever need platform-conditional behavior, like your ns+ macro.
Consider the proposed +clj form in the body of an extend-protocol or a deftype:
(defmacro +clj
"Form is evaluated only in the JVM."
[form]
(when (= :clj *host*) form))
(deftype Foo
SomeCommonProtocol
(aMethod [blah blah] ...)
(+clj
JvmOnlyInterface
(someMethod [blah] ...)
)
)
Of course, this does not work. The deftype macro gets the +clj form unmodified and must handle it on its own.
At best, you could have a syntax-quote style wrapper form:
However, this is essentially what Feature Expressions are anyway. Only broken, since macros don't only operate on syntax/edn, they can operate on any compile-phase value, like dates and times or the output of any tagged literal handler. Without being part of the phase before the macro expander, you can't prevent platform specific types from reaching the macro body.
The reality is that Clojure, in the tradition of most lisps, has rigid read/compile/run phase separation where each phase has a varied evaluation strategy, but the phases run interleaved. Unlike say something like Mathematica, which does not run the reader interleaved with the evaluator - and the evaluator uses normal-order rather than applicative-order! Without such a uniform evaluation strategy (nevermind Mathematica's evaluator's other problems) it simply doesn't make sense to fight so hard against beefing up the read phase.
Related: I've written before about how I don't think that this impacts tooling as badly as you guys thing it does. I just don't understand why you guys are so against feature expressions, nor do I think you've succeeded in proposing a desirable alternative.
Your change is invalid. I picked my example very carefully: You can not extend an interface to an object after the type has been created. That only works with protocols.
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.
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.
Let me rephrase "just hack it": Feature expressions are a low-level primitive for platform switching. If you have cross platform abstractions, consumers of your code shouldn't need to use feature expressions. However, they are very useful in the implementation of such abstractions.
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.
I agree that Feature Expressions don't add power, because yes, they are semantically equivalent to C preprocessing and orthogonal to the idea of Lisp.
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:
1) I don't understand what this example is supposed to demonstrate.
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.
The example demonstrates how you can define a macro that runs its code in Clojure but can emit code to both Clojure and ClojureScript. How would you do this with feature expressions?
This was a great example, and I think you are absolutely right that it
is the identical in function, if not in form, to mine.
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:
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.
That's not equivalent to inline protocol implementation in both Clojure or ClojureScript. The point still stands, the proposal runs afoul of macro composition issues. This isn't to pass judgement on the proposal, but this is a tradeoff that is downplayed.
Given you find the lack of "standard in-memory representation for tagged literals" [1] a problem, I'm wondering why you are so set on adding yet another feature with the same problem and don't understand why some might also be looking for an alternative.
I didn't intend to be snippy or snarky, and I don't really think I was. I was simply pointing out the problems with this proposal, which has been proposed numerous times by numerous folks, but such proposals never addresses the tradeoffs. If you're willing to chat off-list about my communication style, I'm happy to make tone corrections in the future.
As for the "Representing EDN" design doc that I wrote. You're right to note similarity between tagged literals and feature expressions, however, I don't believe that they warrant the same tradeoff. One stated design goal of EDN is for intermediate processors to be able to handle self-describing data. They can already do that by defining a default tagged reader function. All I'm proposing there is that there be a single standard "uninterpreted" type, so that multiple intermediate processors can cooperate in handling self-describing data in the absence of special purpose tagged readers. Otherwise, each reader would need to perform an eager walk of each object to replace unknown types, and, by that point, they have no way of knowing what types they need to look for to replace in general.
By contrast, Clojure's reader already loses information necessary to represent Clojure source code as opposed to EDN data. The EDN data model is not rich enough on its own to represent code.
Fair enough; some of your comments here appeared to me to be dismissive and my perception was wrong.
I should also say I want Niskin and Dipert's efforts here to be taken seriously and I don't want my comment to distract, and for that I apologize to them.
I was just talking about the potential problems of FX last night - very interesting to see such an impressive and simple alternative approach. I'm not sure what the drawbacks are, but looking forward to seeing the discussion that grows from this.
Right, but if feature expressions are part of "Clojure the language" then feature macros can be implemented as a library. The reverse wouldn't work. Ergo, as attractive as I find feature macros, I think this argues that feature expressions are more "fundamental" and therefore deserving to be part of the core language.
... the error being that there is no "gstring" namespace.
Since the proposed solution with macros would produce such code (I am right?), how does it solve the issue of namespaces that only exist in a specific platform?
The Feature Macros proposal is only adding macros and vars to Clojure---the reader isn't modified at all. Since all of the things you mentioned are reader macros (as you pointed out), Feature Macros never even see them, so they won't be affected in any way. (Reader macros are expanded before regular macros are, so regular macros don't see things like `#(...)`, they see `(fn [] ...)`.)
The great point made in the README is that we can't generate feature expressions. With feature macros we can have macros that generate cross-platform code. That's big in my mind.
That hasn't been a problem with reader macros in the past since you don't need to generate `#(...)` if you have `(fn [] ...)`.
I don't understand when I would ever want to generate a feature expression. The goal is to generate platform-specialized code. You never have to generate code that generates platform-specialized code!
Consider the proposed +clj form in the body of an extend-protocol or a deftype:
Of course, this does not work. The deftype macro gets the +clj form unmodified and must handle it on its own.At best, you could have a syntax-quote style wrapper form:
However, this is essentially what Feature Expressions are anyway. Only broken, since macros don't only operate on syntax/edn, they can operate on any compile-phase value, like dates and times or the output of any tagged literal handler. Without being part of the phase before the macro expander, you can't prevent platform specific types from reaching the macro body.The reality is that Clojure, in the tradition of most lisps, has rigid read/compile/run phase separation where each phase has a varied evaluation strategy, but the phases run interleaved. Unlike say something like Mathematica, which does not run the reader interleaved with the evaluator - and the evaluator uses normal-order rather than applicative-order! Without such a uniform evaluation strategy (nevermind Mathematica's evaluator's other problems) it simply doesn't make sense to fight so hard against beefing up the read phase.
Related: I've written before about how I don't think that this impacts tooling as badly as you guys thing it does. I just don't understand why you guys are so against feature expressions, nor do I think you've succeeded in proposing a desirable alternative.