Fork me on GitHub
#beginners
<
2023-01-26
>
cdeln07:01:07

Trying to understand syntax quoting when used with read-string. Evaluating (read-string "(x)")` produces (clojure.core/seq (clojure.core/concat (clojure.core/list 'autodiff.core/x)) , i would expect it to produce either (x)` or perhaps (user.core/x) (assuming expression is evaluated in namespace user.core ). Why does it produce the long expression with namespaced seq, concat and list?

Martin Půda07:01:09

https://clojure.org/reference/reader#syntax-quote For all forms other than Symbols, Lists, Vectors, Sets and Maps, `x is the same as 'x. For Symbols, syntax-quote resolves the symbol in the current context, yielding a fully-qualified symbol (i.e. namespace/name or fully.qualified.Classname).

cdeln07:01:29

That explains why the namespaces are added, but I don't understand why seq , concat and list are generated. It's really when used together with read-string, evaluating (x)` gives (user.core/x).

cdeln08:01:17

I see, the resulting form is equivalent to (user.core/x), and necessary to support splicing somehow (have to understand that later). However, I don't get the same result for the other examples in that thread. When I put ''foo in my repl I get 'foo back (not (quote foo) ), or '#'foo gives me back #'foo (not (var foo)). Is that a clojure/clojurescript difference?

kennytilton08:01:37

Ah, but:

(read-string "''foo")
=> (quote (quote foo))

cdeln08:01:06

Evaluates to ''foo for me xD

kennytilton08:01:28

Oops. Mine was clojure. But

(cljs.reader/read-string "''foo")
''foo

cdeln08:01:50

I'm in clojure too. (read-string "''foo") also gives ''foo for me.

kennytilton08:01:49

My Clojure result as shown stands. We have to be careful with Lisp REPLs; sometimes they try to be nice on the P aspect. Common-lisp has print-readably, which ironically prints human-unreadably, because the reader they have in mmind is the Lisp reader.

😆 2
kennytilton08:01:17

I just checked for that in the CLJS repl:

cljs.user=> (str "test: " (cljs.reader/read-string "''foo"))
"test: ''foo"

cdeln08:01:48

Do you mean that when the repl prints ''foo , it's actually sugaring the underlying expression to make it more human readable?

kennytilton08:01:27

That is my concern, yes. But over here:

~/dev/matrix/cljc/whoshiring [main] $ clj
Clojure 1.11.1
user=> (read-s(read-string "''foo")
(quote (quote foo))
user=> (read-s(read-string "``foo")
(clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list (quote user/foo))))
user=> 
No idea why the "(read-s" is getting echoed into my expressions, but I am not typing them.

cdeln08:01:35

Ok this is interesting. Everything I typed above was through Cider in Emacs. If I run it like you do with clj, I get the same results!

kennytilton09:01:27

That is why I switched to CLJ. My results originally were using a REPL launched in IntelliJ, thought that might be a factor.

kennytilton09:01:27

This shows the myriad printer control variables in clojure: https://clojuredocs.org/clojure.pprint/write FWIW

kennytilton09:01:56

And even with the CLJ console, who knows how ... well...

user=> *print-*print-readably*
true

cdeln09:01:36

Also true in my cider repl actually

kennytilton09:01:52

The moral: looking at the console can be misleading at the edges of any Lisp syntax. What we see is not always what we have. As for the backticks, hmm, we do not have a backquote. So all that code is what the reader generates when it encounters a backtick. Guessing.

kennytilton09:01:32

Come to think of it, `foo is not much of an expression to be backquoting. All that generated code makes more sense for sth like:

(defmacro prog1 [& body]
  `(let [result# ~(first body)]
     ~@(rest body)
     result#))

cdeln09:01:02

I see. Yeah, expression like `foo is just to boil down to understanding backquote. What I am actually doing is to parse a backquoted expression from source code, and it blows up to this huge datastructure, which got me really confused.

cdeln09:01:27

(read-string (source-fn 'when-let) to be more specific

cdeln09:01:45

Indeed, I would expect the syntactic backtick sugar to expand into something that is more 1-1 with the source code, such as a backquote special form. Now I know things get expanded more than that during the read-step!

👍 2
didibus16:01:05

What you see is what syntax quote expands too I believe. Syntax quote and quote are not special forms, they're just reader macros.

cdeln17:01:17

Right. Please explain more if you can, I am still struggling to understand this well 🙂 As I understand it, quote (`'x`) expands into the special form (quote x) in the reader (which is a special form right?). I still don't understand why (x)` and '(x)` expands into different things (the latter being the same as you get when used together with read-string , I think).

didibus17:01:30

Well, to be more precise ` and ' are not special forms, they are reader macros. quote is a special form. The ' reader macro expands to quote . The reader macro on the other hand expands to code that will create a seq of what's inside it and possibly have some stuff in it wrapped in unquote` or unquote-splice See this stackoverflow for more details: https://stackoverflow.com/a/3704703/172272

didibus17:01:34

Like the Stackoverflow says, probably syntax quote could have also expanded into a special form or even a call to a syntax-quote macro that then performs the same transformation of returning that seq concat code. But instead they did it all inside the reader macro, which in Clojure is Java code.

cdeln17:01:55

So there is some underlying hidden black magic going on! 😉 Ok this helps alot

didibus17:01:34

Yes, but I wouldn't say it's hidden black magic. The reader takes text and returns code. It's allowed to return whatever code it wants for whatever text it gets. In other Lisps, you can even add your own reader macros, Clojure doesn't let you, it has a restricted set of them. It's kind of similar to how macros take code and return code and are allowed to return any transformed code they want. In a way each macro is always just black magic that does whatever it wants and you need to learn about each macro to figure out what they do. But at the same time, it's a regular pattern: Text goes into the reader, certain tokens invoke Reader macros that transform the following text in a special way, code is returned. Then code is macro expanded with normal macros, meaning code that follows macro forms are transformed into different code by the macro, and finally the code is evaluated.

didibus17:01:39

So nothing is more special about than about ' , they both take text and returns code. The only surprising thing is how complicated and not intuitive the code returned by is in comparison to '

cdeln17:01:54

So reader macros operate on the input text, while normal macros operates on lisp data?

didibus17:01:07

Correct. That's the difference between reader macros and normal macros. It's also why writing a reader macro is a lot harder, but technically a lot more powerful. A reader macro could for example take JavaScript source text and return parsed Clojure code from it. It's kind of why Clojure chose not to support them, already macros can make different code base feel like different programming languages since they can add so much new semantics. Reader macros can also change the fundamental syntax , which can make working in two code base really different. So Clojure didn't allow them to try and keep things a bit more consistent.

cdeln17:01:30

Aha! Now when you mention it, I've heard about sorcery like that being used in Scheme. Now I think I understand what that means also.

didibus17:01:14

I like to think of it as reader macros can alter syntax, and normal macros can alter semantics.

cdeln17:01:08

That's a nice take, I'll bring that with me 🙂