clojure

2025-09-12T13:07:14.882079Z

is it intentional that spec/keys will check the :opt-un keys of a map? it's nice that it works, but the docstring merely says that :opt is only for documentation and that there's -un versions.

✅ 1
Alex Miller (Clojure team) 2025-09-12T13:13:38.179879Z

Yes

👍 1
frankleonrose 2025-09-12T15:51:37.166399Z

Is there a reader affordance for reading an entire form from input stream without, for instance, resolving aliases? (For context, nrepl.transport/tty, https://github.com/nrepl/nrepl/pull/387.) The following example accomplishes it, but relies on internal LispReader$Resolver which didn’t arrive until 1.10 (nrepl builds for 1.8 & 1.9).

(with-open [in (-> ":keyword (next-form)"
                   (.getBytes "UTF-8")
                   io/input-stream
                   io/reader
                   (clojure.lang.LineNumberingPushbackReader.))]
  (binding [*reader-resolver* (reify clojure.lang.LispReader$Resolver
                                (currentNS    [_]            (gensym))
                                (resolveAlias [_ _alias-sym] (gensym))
                                (resolveClass [_ _class-sym] (gensym))
                                (resolveVar   [_ _var-sym]   (gensym)))]
    (second (read+string {:read-cond :preserve} in))))

borkdude 2025-09-12T16:00:13.399089Z

$ clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.9.0"}}}' -M -e 'clojure.lang.LispReader$Resolver'
clojure.lang.LispReader$Resolver

borkdude 2025-09-12T16:00:29.371299Z

nrepl dropped support for 1.8 and 1.9 AFAIK? at least CIDER did

borkdude 2025-09-12T16:00:59.399639Z

you can use another library, like tools.reader or edamame, but I guess you're looking for something built-in?

Alex Miller (Clojure team) 2025-09-12T16:03:06.477769Z

this is the purpose of Resolver so I'd say that's definitely the intended approach for the reader

👍 1
oyakushev 2025-09-12T16:07:50.051059Z

We removed 1.7, but 1.8 is honestly not in big demand either. Can phase it out.

frankleonrose 2025-09-12T16:13:35.313429Z

Which would leave 1.9. I’ll check out what edamame has to offer.

oyakushev 2025-09-12T16:14:19.615579Z

I would advise against going that route, as we strongly prefer nREPL staying deps-free.

👍 3
oyakushev 2025-09-12T16:14:49.387979Z

It is OK, however, to not support this particular bit of functionality on Clojure 1.8 and 1.9

frankleonrose 2025-09-12T16:15:20.936599Z

Thanks! I’ll see what I can do within these constraints.

❤️ 1
borkdude 2025-09-12T16:48:12.429019Z

I just showed that this class is available in 1.9?

frankleonrose 2025-09-12T16:55:20.580239Z

Yes, you are correct @borkdude. Thanks. Sadly, Unable to resolve symbol: read+string in 1.9 context.

oyakushev 2025-09-12T17:09:19.900179Z

@frankleonrose You can probably replace read+string with some input stream proxy that captures read bytes for later. Some LLM can help for sure.

borkdude 2025-09-12T17:21:08.138009Z

quickly whipped this up:

(set! *warn-on-reflection* true)

(defn string->input-stream
  [^String s]
  (java.io.ByteArrayInputStream. (.getBytes s "UTF-8")))

(defn read+string*
  "Reads one form from input-stream, returning [form string-read]."
  [^java.io.InputStream is]
  (let [sb (StringBuilder.)
        base (java.io.InputStreamReader. is "UTF-8")
        rdr (java.io.PushbackReader.
             (proxy [java.io.Reader] []
               (read
                 ([] (let [c (.read base)]
                       (when (not= -1 c)
                         (.append sb (char c)))
                       c))
                 ([^chars cbuf ^long off len]
                  (let [n (.read base cbuf off len)]
                    (when (pos? n)
                      (.append sb cbuf off n))
                    n)))
               (close [] (.close base))))
        form (read rdr)]
    [form (str sb)]))

(prn (read+string* (string->input-stream "#_{} ;; dude
     (+ 1 2 3)  (assoc {} :foo :bar)")))

;; => [(+ 1 2 3) "#_{} ;; dude\n   (+ 1 2 3)"]

👍 1
borkdude 2025-09-12T17:22:28.997599Z

what would be a test case to test if the unread on a pushbackreader would work ok here?

oyakushev 2025-09-12T17:22:51.729569Z

But honestly, I'd just skip this feature for 1.9, let it stay "broken" the way it is now

borkdude 2025-09-12T17:23:04.467839Z

yeah or drop support for 1.9 completely

👍 1
oyakushev 2025-09-12T17:24:17.399749Z

That too. nREPL is kinda foundational though, and you never know how people might use it. 1.8 is the last pre-spec and pre-clojure-split version, so there might still be people relying on these characteristics somewhere

borkdude 2025-09-12T17:25:39.429989Z

btw why was the reader resolver needed, isn't it a matter of setting *ns* correctly?

(ns prior
  (:require [clojure.set :as set]))

(ns other)

(binding [*ns* (find-ns 'prior)]
  (prn (read-string "::set/dude")))

oyakushev 2025-09-12T17:28:48.947369Z

IIRC, the tty transport is a weird amalgamation of streaming (tty) and nrepl (message-based). Reading is needed just for framing, to split the text stream and then feed to nrepl server chunk by chunk. Doing what you suggest requires back-n-forth, communicating the "current namespace" back from nrepl server to the tty transport. Probably doable too, but I can't tell what is easier without looking.

borkdude 2025-09-12T17:29:26.746249Z

ok

frankleonrose 2025-09-13T16:23:59.141669Z

Updated example above to return (gensym) for unused resolved symbols. It avoids duplicate key error when parsing forms like {::foo/x 1 ::bar/x 1}. Many thanks for https://clojure.atlassian.net/browse/CLJ-2359?focusedCommentId=50541, @borkdude!

👍 1
eraserhd 2025-09-12T16:24:12.258359Z

I feel like I'm going insane... since when is (= 0.0 0) ;=> false?

dpsutton 2025-09-12T16:25:30.127349Z

float vs integer are never equal

dpsutton 2025-09-12T16:25:49.328459Z

registry=> (= 0 0.0)
false
registry=> (= 0M 0.0)
false
registry=> (= 0M 0N)
false

dpsutton 2025-09-12T16:25:53.491019Z

there are many zeros

dpsutton 2025-09-12T16:26:20.387029Z

but

registry=> (doc ==)
-------------------------
clojure.core/==
([x] [x y] [x y & more])
  Returns non-nil if nums all have the equivalent
  value (type-independent), otherwise false
nil
registry=> (== 0 0.0)
true

➕ 1
eraserhd 2025-09-12T16:26:33.795689Z

I remember people doing a whole bunch of work to make sure the hash codes were the same? Ah... OK

dpsutton 2025-09-12T16:27:02.325939Z

normal caveats of floats though

registry=> (== 0.3 (+ 0.1 0.2))
false

eraserhd 2025-09-12T16:27:48.612069Z

The docs for = say: "compares numbers and collections in a type-independent manner"

dpsutton 2025-09-12T16:28:45.488169Z

hard to parse

dpsutton 2025-09-12T16:28:57.028919Z

how does that distribute?

dpsutton 2025-09-12T16:29:19.772529Z

“compares numbers and also compares collections in a type independent manner” or “compares numbers type independent and compares collectiosn type independent”

eraserhd 2025-09-12T16:30:30.496009Z

ermm, the first interpretation suggests it wouldn't compare, e.g. strings, or symbols, or ...

eraserhd 2025-09-12T16:31:00.218379Z

Oh... I see, it could be that it specially handles numbers.

eraserhd 2025-09-12T16:31:57.270759Z

Still, that doesn't seem like the natural interpretation.

dpsutton 2025-09-12T16:32:00.325119Z

ok. i think the type independent equality is there

dpsutton 2025-09-12T16:32:22.997109Z

(= (short 1) (long 1))
true

dpsutton 2025-09-12T16:32:29.815339Z

but it doesn’t cross categories

dpsutton 2025-09-12T16:34:01.653519Z

(= (short 1) (long 1) 1N)
true

dpsutton 2025-09-12T16:34:25.869909Z

(= (short 1) (long 1) 1N (int 1))
true

eraserhd 2025-09-12T16:36:18.524069Z

Ah, OK, so https://clojure.org/guides/equality does explicitly call out that they need to be the same category.

dpsutton 2025-09-12T16:38:06.670879Z

fun thread to go down. I suspect that’s an @andy.fingerhut document . He’s done some really great things like this

eraserhd 2025-09-12T16:40:50.759739Z

clojure.core/=
([x] [x y] [x y & more])
  Equality. Returns true if x equals y, false if not. Same as
  Java x.equals(y) except it also works for nil, and compares
  numbers and collections in a type-independent manner.  Clojure's immutable data
  structures define equals() (and thus =) as a value, not an identity,
  comparison.
I'm still not sure how to interpret "numbers" in this docstring though.

eraserhd 2025-09-12T16:41:02.092419Z

(have to run, though)

dpsutton 2025-09-12T16:41:45.798349Z

can you write a question that you don’t know how to answer?

oyakushev 2025-09-12T17:13:11.851579Z

= and floats, name a more iconic duo

2025-09-12T17:35:31.811409Z

Yes, the equality document was me trying to carefully and thoroughly present the full behavior of clojure.core/=, along with a few other related things like clojure.core/hash , warts and all.

1
🙏 1
2025-09-12T17:35:57.804589Z

(different people may have different opinions of what parts should be considered "warts" vs. not, of course).