clojure-dev 2026-10-01

Looks like a regression in Alpha 8: something breaks Meander:

(2026-10-01.07:25:20)-(~/clojure)
(!2007)-> clojure -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.13.0-alpha7"} meander/epsilon {:mvn/version "0.0.650"}}}' -M -e "(require 'meander.epsilon)"
Reflection warning, meander/util/epsilon.cljc:758:24 - reference to field val can't be resolved.

(2026-10-01.07:26:12)-(~/clojure)
(!2008)-> clojure -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.13.0-alpha8"} meander/epsilon {:mvn/version "0.0.
650"}}}' -M -e "(require 'meander.epsilon)"
Reflection warning, meander/util/epsilon.cljc:758:24 - reference to field val can't be resolved.
Syntax error compiling at (meander/epsilon.clj:60:6).
Unable to resolve symbol: let in this context

Full report at:
/tmp/clojure-9903279887696048691.edn

πŸ‘€ 1

Where is meander/epsilon.clj?

(defn compile-k-map-specialized-matrix
  {:private true}
  [k [target & rest_targets :as targets] matrix]
That destructuring fails.

This is the invocation in epsilon.clj:

([x pattern expr]
   (with-meta `(meander.match.epsilon/match ~x ~pattern ~expr)
     (meta &form)))

Hrm, sequential destructuring has not changed

let is a special form and excluding it is not ok

let* is right?

Not according to the docs. let* is an implementation detail

FWIW, here's the code: https://github.com/noprompt/meander/blob/74de6b1f651441092cc12d1c9012ef7086033040/src/meander/epsilon.clj#L2 I've run into this stuff with SCI as well where I didn't fully qualify let People do it if they want to define their own let macro in a library

I'm not defending them, just observing that they do it ;)

let is a special form of the language, you can't redefine it. Some things have to be reserved for the bottom.

(clj-kondo linter incoming)

I mean, library authors sometimes offer their own my-library/let macro, which is fine? People who are using it don't redefine let, usage is often aliased. Another example of this is promesa/let , a JS-promise aware let macro. Library authors probably use :exclude [let] to avoid the compiler warning in the namespace where they define the macro. I can give more real examples of this from the wild if you want.

No it's not fine. The special forms define the language. What they mean is up to the language, and they should mean what the language says they mean, everywhere.

People should respect the documentation and not rely on implementation details.

Everything on the special forms page is off limits for redefinition

I go for breakfast and... 😁 So, this is just broken code that happens to have always worked before, right?

This would have saved me headaches too, if the compiler didn't allow redefining these special forms, but people do it, even if it's against the documentation. Now it's time for my breakfast. :-D

can the compiler warn in such cases?

here's an ask about this

A special form in call position is prioritized over locals and vars normally. That isn't true for let

user=> (let [let 1] (let [x 1] x))
Syntax error compiling at (REPL:1:14).
Unable to resolve symbol: x in this context
vs
(let [def 1] (def x 1))
also:
user=> (special-symbol? 'let)
false
Documentation says otherwise, but people don't read documentation, often. :-s

jumping here to add that the inline meta change also breaks ClojureScript

@richhickey by "off limits for redefinition" do you mean 1. nothing should ever be bound to the symbol let anywhere, so there should be no promesa.core/let or 2. let on its own should never mean anything else than the let special form, but a qualified promesa.core/let is still allowed?

the problem is that ClojureScript redefines many core macros more less so needs to exclude them. So we exclude so we can cleanly redefine, and refer to the original via namespacing.

Does one think they can redefine long or if in Java? One of the first things one does in learning a language is learning the reserved words/symbols. The special forms are those for Clojure.

πŸ‘ 1

Cljs is an implementation so somewhat different

People do wrong things all the time unless they are forced not to (by compiler, linter. etc.)

βž• 2

N.B. I am not saying we won't qualify this let, but people who are redefining special forms should expect to break in the future with no recourse.

to clarify a bit why ClojureScript hit this issue. In that past ClojureScript mostly used the macros in Clojure because they didn't have host details. Over time we had to move things into ClojureScript because of performance (and/or is slow because of a cascade of CLJ truth-y checks), or other details like support code movement optimizations via Closure Compiler. None of these imply the issue w/ let . let arises because when we added bootstrapping to JavaScript in 2017 well now we can't source macros from Clojure anymore, they must be available in ClojureScript. The reason instance? fails is because we use macros for inlining a la :inline sometimes - because ClojureScript has two namespaces one for fns one for macros this pattern works. And well we inline instance? into a faster JS native test. Again exacerbated by optional bootstrapping.

this is backwards-compatible territory. Even if it wasn't meant to work, libraries depend on the behavior. It's one of those fun ones where there's often no good choice.

this is fascinating for a non-core-clojure dev

FWIW, test.check also redefines let : https://github.com/clojure/test.check/blob/5ba3a25b60cf66ff531db8f44d89145abfacfa5c/src/main/clojure/clojure/test/check/generators.cljc#L1747, although I'm not sure why that doesn't break:

Ξ» clojure -Srepro -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.13.0-alpha8"} org.clojure/test.check {:mvn/version "1.1.3"}}}' -M -e "(require 'clojure.test.check.generators)"
Ξ»

Because it defines it at the bottom of the file, and the only subsequent references to Clojure's let are qualified as core/let.

(it doesn't rely on map destructuring so there's no possibility of introducing an unqualified-`let` there)

I see, makes sense! πŸ‘

People should respect the documentation and not rely on implementation details.
> Everything on the special forms page is off limits for redefinition I don’t think the documentation actually says that you cannot redefine special forms. It only speaks about evaluation order of unqualified symbols. This can be interpreted as (defmacro let) … (myns/let …) being OK.

βž• 1