Clojure

Compiler doesn't throw an error on unhygienic macro using let with :keys

Details

  • Type: Defect Defect
  • Status: Closed Closed
  • Priority: Minor Minor
  • Resolution: Declined
  • Affects Version/s: Release 1.8
  • Fix Version/s: None
  • Component/s: None
  • Labels:
  • Environment:
    Arch Linux

Description

It's possible to create a macro that captures identifiers without running into an error when using it.

Compare

Unable to find source-code formatter for language: clojure. Available languages are: javascript, sql, xhtml, actionscript, none, html, xml, java
(defmacro testmacro [& body]
  `(let [unhygienic 1]
     ~@body))

(testmacro unhygienic) ;=> error

with:

Unable to find source-code formatter for language: clojure. Available languages are: javascript, sql, xhtml, actionscript, none, html, xml, java
(defmacro testmacro [& body]
  `(let [{:keys [unhygienic]} {:unhygienic 1}]
     ~@body))

(testmacro unhygienic) ;=> nil

Activity

Hide
Vasilij Schneidermann added a comment -

More interestingly, the following actually gives you the value in the map:

(defmacro testmacro [& body]
  `(let [{:keys [unhygienic]} {::unhygienic 1}]
     ~@body))

(testmacro unhygienic) ;=> 1
Show
Vasilij Schneidermann added a comment - More interestingly, the following actually gives you the value in the map:
(defmacro testmacro [& body]
  `(let [{:keys [unhygienic]} {::unhygienic 1}]
     ~@body))

(testmacro unhygienic) ;=> 1
Hide
Alex Miller added a comment -

If I'm reading this right, the first example errors because it produces a let binding with a qualified local, which is not allowed.

(let [user/unhygienic 1] ...)

The second example does not, because namespaced key symbols are supported in destructuring :keys (they match the namespaced keyword, then create a local with just the name part, dropping the qualifier).

(let [{:keys [user/unhygienic]} {:unhygienic 1}] ...)

So, it doesn't throw an error because it's not an error.

Declining...

Show
Alex Miller added a comment - If I'm reading this right, the first example errors because it produces a let binding with a qualified local, which is not allowed.
(let [user/unhygienic 1] ...)
The second example does not, because namespaced key symbols are supported in destructuring :keys (they match the namespaced keyword, then create a local with just the name part, dropping the qualifier).
(let [{:keys [user/unhygienic]} {:unhygienic 1}] ...)
So, it doesn't throw an error because it's not an error. Declining...
Hide
Andy Fingerhut added a comment -

Also, I believe it is a design goal of Clojure that you are allowed to write unhygienic macros. It isn't like Scheme's hygienic macro system, and that is not an accident.

Show
Andy Fingerhut added a comment - Also, I believe it is a design goal of Clojure that you are allowed to write unhygienic macros. It isn't like Scheme's hygienic macro system, and that is not an accident.
Hide
Vasilij Schneidermann added a comment -

Sure it's allowed, but on an opt-in basis. I'm used to the language detecting unwanted symbol capture by throwing an error, which I then correct by using the foo# syntax. In case I really want to break hygiene, I can still use ~'foo.

I believe the above example to show a short-coming of the mechanism detecting these cases. Just because it isn't an error doesn't mean the existing behavior is correct.

Show
Vasilij Schneidermann added a comment - Sure it's allowed, but on an opt-in basis. I'm used to the language detecting unwanted symbol capture by throwing an error, which I then correct by using the foo# syntax. In case I really want to break hygiene, I can still use ~'foo. I believe the above example to show a short-coming of the mechanism detecting these cases. Just because it isn't an error doesn't mean the existing behavior is correct.
Hide
Andy Fingerhut added a comment -

You are used to other languages detecting unwanted symbol capture in macro definitions? Or in Clojure doing so? I don't recall cases of Clojure detecting cases of symbol capture and giving an error for them, but maybe you have an example of that?

Show
Andy Fingerhut added a comment - You are used to other languages detecting unwanted symbol capture in macro definitions? Or in Clojure doing so? I don't recall cases of Clojure detecting cases of symbol capture and giving an error for them, but maybe you have an example of that?
Hide
Vasilij Schneidermann added a comment -

Sure, it's in the very first example:

(defmacro testmacro [& body]
  `(let [unhygienic 1]
     ~@body))

(testmacro unhygienic) ;=> Can't let qualified name: user/unhygienic

Anyway, a colleague of mine came up with a more illustrative example demonstrating the unhygienic variable capture:

(defmacro minc [x]
  `(let [{:keys [n]} {::n 1}]
     (+ ~x 1)))

(minc 10)
; => 11

(let [x 10]
  (minc x))
; => 11

(let [n 10]
  (minc n))
; => 2

In case you're wondering why I was using {{(let [{:keys [...]} ...] ...)}} in the first place, it was to extract values from configuration. Clearly it's useful to use that syntax in regular code, but I find its use in macros questionable.

Show
Vasilij Schneidermann added a comment - Sure, it's in the very first example:
(defmacro testmacro [& body]
  `(let [unhygienic 1]
     ~@body))

(testmacro unhygienic) ;=> Can't let qualified name: user/unhygienic
Anyway, a colleague of mine came up with a more illustrative example demonstrating the unhygienic variable capture:
(defmacro minc [x]
  `(let [{:keys [n]} {::n 1}]
     (+ ~x 1)))

(minc 10)
; => 11

(let [x 10]
  (minc x))
; => 11

(let [n 10]
  (minc n))
; => 2
In case you're wondering why I was using {{(let [{:keys [...]} ...] ...)}} in the first place, it was to extract values from configuration. Clearly it's useful to use that syntax in regular code, but I find its use in macros questionable.
Hide
Alex Miller added a comment -

As I said above, the error here is not from symbol capture (that's allowed), it's from syntax quote resolving a qualified local which is invalid:

user=> `(let [unhygienic 1] 2)
(clojure.core/let [user/unhygienic 1] 2)
                   ^^^^^ qualified local bindings are invalid

This has nothing to do with symbol capture or hygiene.

Show
Alex Miller added a comment - As I said above, the error here is not from symbol capture (that's allowed), it's from syntax quote resolving a qualified local which is invalid:
user=> `(let [unhygienic 1] 2)
(clojure.core/let [user/unhygienic 1] 2)
                   ^^^^^ qualified local bindings are invalid
This has nothing to do with symbol capture or hygiene.
Hide
Alex Miller added a comment -

Your second example avoids this:

user=> `(let [{:keys [unhygienic]} {:unhygienic 1}] nil)
(clojure.core/let [{:keys [user/unhygienic]} {:unhygienic 1}] nil)

This is valid syntax in destructuring, where namespaced locals ARE allowed - they match the namespace when destructuring, then bind the local named symbol equivalent (here unhygienic).

Show
Alex Miller added a comment - Your second example avoids this:
user=> `(let [{:keys [unhygienic]} {:unhygienic 1}] nil)
(clojure.core/let [{:keys [user/unhygienic]} {:unhygienic 1}] nil)
This is valid syntax in destructuring, where namespaced locals ARE allowed - they match the namespace when destructuring, then bind the local named symbol equivalent (here unhygienic).
Hide
Andy Fingerhut added a comment -

Vasilij, it sounds like you are thinking of the error as occurring because Clojure has mechanisms intended to help you catch unintended symbol capture.

I don't think it has any mechanisms designed for that purpose.

You are taking an error message that is detecting something else that is wrong with the code (after backtick expansion), and misattributing the reason for that error as Clojure trying to help you catch unintended symbol captures.

Show
Andy Fingerhut added a comment - Vasilij, it sounds like you are thinking of the error as occurring because Clojure has mechanisms intended to help you catch unintended symbol capture. I don't think it has any mechanisms designed for that purpose. You are taking an error message that is detecting something else that is wrong with the code (after backtick expansion), and misattributing the reason for that error as Clojure trying to help you catch unintended symbol captures.

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: