Maybe I'm wrong, but I don't think EDN really has anything to do with homoiconicity. You're just parsing in data (strings) into other data (internal Clojure data structure). It's nice that the syntax matches how you'd write the data structure in Clojure itself.. but you could also parse EDN in a non-homoiconic language.
Clojure's lispyness is a bit overplayed? I feel it's generally irrelevant for the vast majority of language users and it's really not what makes the language great. I seldom seeing custom macros... and while I'm sure it's important for a minority and I'm glad it's there - if it was disabled tomorrow (outside of the language internals) I'd probably take me a while to even notice :)
Clojure's lispyness is massively underrated. At Hyperfiddle we have a fully reactive Clojure/Script dialect, backed by a functional effect system https://github.com/leonoel/missionary. Our reactive clojure can also distribute across the client/server system, like distributed map reduce. Imagine React.js but full stack, incremental view maintenance all the way from database views to DOM views as one streaming computation. We implement a custom analyser like core.async for full compatibility with host clojure, including preexisting macros, so it gets destructuring, control flow, higher order logic, it's a first class Clojure in every way. What you are measuring I think is that metaprogramming is hard, the books and papers about it are hard, the intersection of metaprogramming with functional programming isn't even well understood in research, and businesses don't invest in hard things so you won't get paid to learn any of this.
The thing about macros, it's not about day to day, or even month to month, macros are about year to year. Macros are what let programers expand the language in ways the designers never even considered, thus increasing the timescale over which your code is maintainable.
For a recent example of why you wish your language had macros, look at function builders in Swift. When SwiftUI was first announced its syntax was a huge wtf, only later did we find out that a new language feature was enabling this syntax.
Now imagine if somebody outside of Apple had wanted to do this and had to convince the Swift dev team to incorporate this feature...good luck.
In any given lisp, function builders are a 10 minute project to make a macro.
> Maybe I'm wrong, but I don't think EDN really has anything to do with homoiconicity. You're just parsing in data (strings) into other data (internal Clojure data structure).
Part of the very real value in being able to print the data exactly as you'd type it in as a literal is repl-driven development and production logging. I can effortlessly print something to the production logs, copy/paste it into my development environment, and debug. So while it's true that string (de)serialization is an old idea, very few languages support one-to-one copy/paste (de)serialization. So often, other languages give you <Object_0xffa003> or something if you try to call x.toString(). Clojure makes it easy to move data around between systems, by emphasizing extensible, yet simple data structures.
Then eval that in the repl and copy the result back into the test file. It’s a bit clunky because the repl output uses fully qualified symbols, but cleaning that up is usually faster that manually typing out the code it generates.
Macros are indispensable for making libraries! Take a look at the source code of any one of the libraries you use on a daily basis and search for "defmacro" and you might be shocked =)
I understand what you are trying to say regarding macros and I think you are correct that many probably do not define their own macros and happily use the language as is. However, I think if macros are disabled everyone will notice right away.
(macroexpand '(defn foo [x] (println x)))
Returns:
(def foo (clojure.core/fn ([x] (println x))))
Clojure itself is built from macros. Even something simple like defining a named function with defn.
Maybe I should have left off the last part b/c it's a bit provocative :) Yeah, the language itself is built on macros - but as a language user you don't really care as it's an implementation detail. I guess my point is that you could know nothing about macros and still use Clojure very productively. Selling Clojure as another Lisp feels like sell a car for its cupholders. The data structures, syntax, JVM interop, REPL etc. are more interesting and have little to do with Lisp. But that's just my opinion as a casual user
But the reason you care is because the language is built out of the same machinery that you the developer have access to. It's not only an implementation detail because you have access to it as well.
I might be old school, but I thought the whole point of EDN was it's something of a JSON alternative.
As for macros, speaking from experience, I think custom macros are rare because there are only three real use cases: First, was to reduce duplicate code. Second, was to extend the language if it was missing a feature. Third was to prevent expansion of some form (ie logging out a infinitely expanding form). It's a hard syntax to grasp sometimes with all the `~@form type stuff. I used to chastise developers on our team for writing macros because they would often write macros that really did none of those (like a custom thread-> macro). They are pretty _fun_ to write though! :)
Edit: I wanted to add some more thoughts to this. I also think the lispyness of Clojure is overplayed to the end developer, but a large amount of the Clojure core language forms are macros (and, or, ->, for, etc). I'm speculating here, but I'm guessing it was how the Clojure core language became relatively less verbose than other lisp dialects.
Rich Hickey goes into detail how he designed and implemented Clojure. One thing that stood out was a focus on a really small, conservative core, where the vast majority of clojure’s features are implemented as libraries, based on a small, core language. Other languages would need to extend the syntax to achieve the same.
A great read, and I don’t program Clojure in any respect, but I do want to after reading it.
Thanks for the link. I'll take a read when I have some time :)
If there is a small core, it's not necessarily obvious. Yes, a lot of things are pushed out to libraries, but even the basic language features, liked (defn) (cond) (ns) threading etc. - are all implemented through macros. There is prolly a small subset of the language is which is actually made of language primitives implemented in Java or JS. From a language development point of view this probably makes life much easier. And if you wanted to port Clojure to a different backend then that'd probably be much easier than a language where syntax is added more explicitly. There is a design elegance to it, but as a language user it really makes no difference if a feature is a macro or language primitive. I mean to say Clojure isn't big, but it's not some barebones Scheme-y thing either. It probably has a Scheme-y core, but you'd need to root around to find it
yes, it's just a data format that can be recursive. it should really replace all the json, yaml, toml and everything else. after using edn i simply hate every other format, they are painful :(
Custom macros are very commonly exported by libraries, but it also has nothing to do with homoiconicity, Rust supports a fairly similar procedural macro model as well as a more higher level pattern matching one and isn't homoiconic.
A macro system that manipulates the syntax tree directly has little to do with homoiconicity.
The real advantage is simply that the syntax is very simple and consistent but there is no need for it to be homoiconic to be that.
"Unlike most programming languages, Lisp supports parse-time execution of programs, called "read macros" or "reader macros". These are used to extend the syntax either in universal or program-specific ways."
read-string is a convenience over calling read itself as it will take a string rather than needing you to create a java.io.PushbackReader yourself.
As such read and read-string were implemented as core to Clojure's self interpretation and implementation, not as part of a general safe serialization API.
Unfortunately the temptation was to reach for read-string for de-serialization in general as it was so convenient and in the core. In a dev setting where you control and trust all input that is fine. In other contexts it definitely is not!
Yes you can, but they are a bit constrained in what they can do. For instance, it's easy to write a debug macro that prints the intermediate value of b+c:
(let [a #d (+ b c)] ...)
So the #d reader macro has access to the following form, but gets ignored at a higher level (i.e. when the "let" form is processed)
The clojure reader supports the #= prefix before a form, which will cause the reader to read the following form, pass it to eval, and use the result as if it were part of the passed in data.
having built my own Lisp by learning Clojure I think Clojure does broke some ability of homoiconicity in small features.
Say I have `{:a 1}`, which is equivalent to `(hash-map :a 1)`, I take it as data and I can recreate the expression with the later form:
user=> (quote (hash-map :a 1))
(hash-map :a 1)
user=> (def a (quote (hash-map :a 1)))
#'user/a
user=> (list (nth a 0) (nth a 1) (nth a 2))
(hash-map :a 1)
but for `{:a 1}`, I got error since it's a bit different:
user=> (def b (quote {:a 1}))
#'user/b
user=> (list (nth b 0) (nth b 1) (nth b 2))
Execution error (UnsupportedOperationException) at user/eval12 (REPL:1).
nth not supported on this type: PersistentArrayMap
user=> b
{:a 1}
now it's different, it's no longer homoiconic in this syntax.
user=> (first b)
[:a 1]
I would rather not using this syntax in my own lisp and still using a prefixed version.
Clojure's lispyness is a bit overplayed? I feel it's generally irrelevant for the vast majority of language users and it's really not what makes the language great. I seldom seeing custom macros... and while I'm sure it's important for a minority and I'm glad it's there - if it was disabled tomorrow (outside of the language internals) I'd probably take me a while to even notice :)