clojure

Jim Newton 2025-12-04T12:35:18.408359Z

I’m reading the Sequence documentation here: https://clojure.org/reference/sequences and something important seems to be missing, unless I’m misreading it. How can I know if an object is an object on which seq will return a sequence? If I understand correctly seq returns something for which seq? will return true. Did I understand that correctly? But if I call seq on something that doesn’t obey the sequence protocol, the throws and exception rather than returning nil and (seq? [1 2 3]) returns false.

Jim Newton 2025-12-04T12:35:59.260689Z

is the correct way to call seq inside a try/catch?

Jim Newton 2025-12-04T12:37:06.603459Z

or is the correct way to use the sequential? function. It seems like that should be explained as part of the sequence abstraction documentation. No?

Jim Newton 2025-12-04T17:26:10.744229Z

@bg, in my opinion this she be mentioned on https://clojure.org/reference/sequences no?

bg 2025-12-04T17:41:16.481539Z

@jimka.issy agreed... @alexmiller is the best person to decide on this matter.

Alex Miller (Clojure team) 2025-12-04T17:42:45.328939Z

You can file an issue at https://github.com/clojure/clojure-site/issues

Alex Miller (Clojure team) 2025-12-04T20:04:15.851169Z

I added "Check if a coll can produce a seq: https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/seqable?" to the sequences page. if that's not enough, then please file an issue

👍🏻 1
👍🏾 1
ghadi 2025-12-04T20:37:13.053219Z

in general it's very rare to check

ghadi 2025-12-04T20:38:17.515199Z

only libraries that operate on opaque args need to check. 99% of the time you call seq or you call a coll fn like map that calls seq for you

Jim Newton 2025-12-04T21:26:59.295279Z

@alexmiller wow, thanks. that’s great

Jim Newton 2025-12-04T21:33:42.009889Z

@ghadi i’m trying (not finished yet) to implement a more restrictive(less permissive) equal where two sequences are equal only if their types are equal and their content is equal. So I want [1 2 3] and (1 2 3) to NOT be equal and I want [[[[[(1)]]]]] and [[[[[[1]]]]]] to be unequal. but (1 (2 (3))) and (1 (2 (3))) to be equal. So I need to know if I can walk the structure using first and rest.

Jim Newton 2025-12-04T21:34:53.300989Z

Here’s my current attempt, not fully tested

(defn strong-equal [a b]
  (or (identical? a b)
      (and (identical? (type a) (type b))
           (if (seqable? a) ;; does a obey seq abstraction?
             (loop [as a
                    bs b]
               (cond (and (empty? as)
                          (empty? bs))
                     true

                     (or (empty? as)
                         (empty? bs))
                     false

                     :otherwise
                     (and (strong-equal (first as) (first bs))
                          (recur (rest as) (rest bs)))))
             (= a b)))))

ghadi 2025-12-04T21:35:29.480779Z

brittle-equal

2025-12-04T21:35:34.600119Z

Watch out, that'll fail in some strange and annoying ways

Jim Newton 2025-12-04T21:35:43.436149Z

tell me.

2025-12-04T21:37:21.489999Z

(= (type (list 1 2 3)) (type (take 3 (list 1 2 3))))
;; false

ghadi 2025-12-04T21:38:08.907039Z

where two sequences are equal only if their types are[1 2 3] is not a sequence (it's sequential though)

2025-12-04T21:39:18.311769Z

(= (type {:a 1 :b 2}) (type (zipmap (range 10) (range 10))))
;; false

ghadi 2025-12-04T21:39:27.960609Z

all of this is mechanical anyhow... why are you making this fn in the first place?

Jim Newton 2025-12-04T21:39:43.695809Z

yes [1 2 3] is not a sequence, but = thinks it is equal to (1 2 3)

2025-12-04T21:39:49.812729Z

a lazy seq prints like a list and acts like a list but is not a PersistentList. (cons 1 (cons 2 (cons 3 nil))) looks like a list but is not a PersistentList

Jim Newton 2025-12-04T21:40:43.155939Z

@noah, those are good examples. yes I want those to be considered unequal.

Jim Newton 2025-12-04T21:41:06.325389Z

at least for my experimental application.

2025-12-04T21:41:40.890129Z

i think you'll find that clojure is really annoying and won't work great if you go this route

2025-12-04T21:42:17.201319Z

for example:

(= (type (zipmap (range 8) (range 8)))
   (type (zipmap (range 9) (range 9))))
;; false

👏🏻 1
Jim Newton 2025-12-04T21:42:55.324619Z

oh that is indeed surprising. why are those types not equal? but cases like that sound like precisely the cases I want to detect as not being strong-equal.

2025-12-04T21:43:47.812289Z

map literals with 8 or fewer entries use PersistentArrayMap, maps with more than 8 entries use PersistentHashMap

✔️ 1
2025-12-04T21:45:28.722349Z

But that's purely an implementation detail and should not be relied on.

(= (type (zipmap (range 8) (range 8)))
   (type (dissoc (zipmap (range 9) (range 9)) 8)))
;; false

2025-12-04T21:45:47.589619Z

as you can see, when you shrink a PersistentHashMap, it isn't moved back to a PersistentArrayMap

2025-12-04T21:47:00.681719Z

or at least it's undefined what the concrete class of a "map-like" object in clojure will be

🎯 1
2025-12-04T21:47:26.961139Z

so maybe some internal function will convert to a PAM, while another won't. The interface is the only thing that matters

Jim Newton 2025-12-04T21:48:47.643829Z

the clojure implementation very often treats such lists differently, and does a lot of work to detect such cases. certain types of seq-able objects behave very differently. For example (fn [x] x) is not the same as (fn (x) x) the implementation treats those as not-equal.

Jim Newton 2025-12-04T21:49:23.197709Z

when manipulating data which is code, it is dangerous to consider those as being equal.

2025-12-04T21:49:39.631559Z

(fn [x] x) is not checked with =, it's checked with vector?

Jim Newton 2025-12-04T21:50:19.584169Z

(fn [x] x) is not a vector

Jim Newton 2025-12-04T21:50:33.827049Z

is it?

p-himik 2025-12-04T21:50:56.109509Z

Noah meant the [x] bit. Check out (source fn).

Jim Newton 2025-12-04T21:52:20.925699Z

yes exactly. my point is that the when dealing with data which is code, it is dangerous to do things like (= sample-code '(fn [x] x)) because this will return true also when sample-code is (fn (x) x). I need a stronger form of equal.

2025-12-04T21:54:22.924909Z

in my linter #splint, i use vector? and list? because i'm working on literals. if i was operating on code that could be constructed, i'd use vector? and (and (sequential? foo) (not (vector? foo)))

Jim Newton 2025-12-04T21:55:10.351569Z

that is pretty common to write very complicated code to detect that things are not really equal which = thinks are equal.

2025-12-04T21:55:54.299369Z

clojure is generally not interested in the concrete type of a given object

Jim Newton 2025-12-04T21:56:50.737599Z

in order to use vector? and list? you have some surrounding code which is walking the structures and checking individual types of objects. which is what my proposed strong-equal function (above) does.

p-himik 2025-12-04T21:57:37.003919Z

> it is dangerous to do things like (= sample-code '(fn [x] x)) And the point is, in general checks like this aren't useful. Useful checks end up being simpler (vector? some-part) or something like that. But I understand that your case might be different. Perhaps, comparing the results of pr-str is the way? :D Won't work for lazy seqs though, if you use them.

Jim Newton 2025-12-04T21:57:50.158509Z

> clojure is generally not interested in the concrete type of a given object agreed, so writing code that does care can be tricky. and I’m sure there are lots of gotchas.

2025-12-04T21:58:40.474469Z

you've not said the goal of these questions. is this for your class to check their homework?

2025-12-04T21:59:18.745289Z

we might be in an "x y" situation where we're answering your question but not giving you a nicer solution to your problem

p-himik 2025-12-04T22:03:42.285979Z

At least partially, the XY situation could be glimpsed in this thread: https://clojurians.slack.com/archives/C03S1KBA2/p1763478809076079 @jimka.issy You said in that thread: > if I rewrote it today I’d probably choose to use sexpressions for the reader syntax, and some constructed data structure wrapper as form to indicate, trustable data. To which I replied: > If you have plans for this software, such a rewrite might still be the best time investment. But you're the better judge here. And this current thread is yet another demonstration for why I still maintain that response.

Jim Newton 2025-12-04T22:04:23.379639Z

it’s a bit complex to explain the entire problem. Basically have a library that represents types as possibly infinite sets of objects. One way I want to represent a type is by expressly specifying its members. another way to specify a type is with a predicate function. another way to specify a type is by specifying its type as per the type function. still another way to specify a type is a Boolean combination and/or/not of other types. After types are defined, I want to ask questions about membership, intersection, emptyness, subsetness, etc. When I ask whether (1 2 [3]) is member of the type that should be a different question then whether [1 2 [3]] and (1 2 (3)) are members of the type. Not sure if that explaation helps or makes it more confusing.

2025-12-04T22:06:27.446599Z

i'm not further confused, but i don't know enough about that domain to know how to help. i hope that you've looked into clojure.spec or malli to help. those are good at things like "type as predicate function" or "type as concrete type"

Jim Newton 2025-12-04T22:09:16.542829Z

the main different from RTE as compared with malli and clojure.spec is that RTE uses regular-language theory and deterministic-finite-automata to specify regular sets of sequences. So questions about subsetness, emptyness, become solvable problems.

Jim Newton 2025-12-04T22:09:58.525069Z

I haven’t actually found any academic papers on malli. Does anyone know whether such exists?

2025-12-04T22:10:05.921219Z

i suspect not

p-himik 2025-12-04T22:10:28.011569Z

You've asked that before around half a year, maybe a year ago. :) I doubt the answer has changed.

Jim Newton 2025-12-04T22:12:31.698109Z

@p-himik, yes exactly but Noah was asking about malli. There are also another similar system for clojure. One by Christophe Grand (seq-rex I think) and I talked to him. he said his system was not based on finite automata because he believed this would be susceptible to certain limitations in the JVM about code size.

Jim Newton 2025-12-04T22:13:23.895809Z

In the end if my research topic proves useless, I will have learned a lot in trying to understand why it won’t work.

2025-12-04T22:14:48.669219Z

there's https://github.com/pangloss/pattern/ for general sexp pattern matching, which might be helpful in your search

Jim Newton 2025-12-04T22:19:07.883959Z

@Noah, Thanks for the reference. I have the book that software is based on, but I never finished reading it.

👍 1
Steven Lombardi 2025-12-04T19:56:27.471899Z

I was sharing this with folks at Conj, but in case you didn't get to be a part of the discussion, here's your fun Clojure fact for today:

clj꞉user꞉> 
(let [some-seq (map inc [1 2 3])]
  (realized? some-seq))
false

clj꞉user꞉> 
(let [some-seq (with-meta (map inc [1 2 3]) {:tag "hello"})]
  (realized? some-seq))
true

Steven Lombardi 2025-12-04T19:57:40.764689Z

Not an issue for me. I'm just wrapping my seqs in my own IObj to avoid this behavior where necessary.

borkdude 2025-12-04T19:57:56.480129Z

@lambeauxworks did you submit this as a bug to CLJS?

borkdude 2025-12-04T19:58:09.470859Z

oh wait, it's JVM Clojure?

borkdude 2025-12-04T19:58:43.309439Z

I misread clj꞉user as cljs.user which is the name of the default CLJS namespace :)

Steven Lombardi 2025-12-04T19:58:51.893769Z

Correct. JVM Clojure. I have not submitted it as a bug to anything because I wasn't sure if laziness was considered an "implementation detail".

borkdude 2025-12-04T19:59:11.874189Z

seems like a bug to me

dpsutton 2025-12-04T19:59:18.647939Z

i’ve always heard the degree of laziness is not anything promised by Clojure

Steven Lombardi 2025-12-04T20:00:23.994269Z

I agree it seems like a bug in that it strikes me as surprising behavior. But I also have heard the same, Dan.

borkdude 2025-12-04T20:00:28.141459Z

ah it seems only the first chunk is realized

dpsutton 2025-12-04T20:00:43.979159Z

yes realized doesn’t mean the entire sequence right?

Steven Lombardi 2025-12-04T20:01:20.867689Z

Pretty sure that is the case, but I can verify...

dpsutton 2025-12-04T20:01:31.827389Z

(let [s (with-meta (map inc (range)) {:infinite true})]
  (realized? s))
true

👍 1
dpsutton 2025-12-04T20:01:42.680049Z

infinite sequence is a good example

borkdude 2025-12-04T20:01:46.289459Z

oh doesn't it mean that? that's even more confusing to me

dpsutton 2025-12-04T20:02:22.422559Z

my handwavy memory of realized is to never use it for whether a lazy sequence is realized or not, but more for promise and delays

☝️ 1
1
dpsutton 2025-12-04T20:02:48.819419Z

and that if you are fighting to keep a particular amount of laziness you are off the happy path

seancorfield 2025-12-04T20:03:16.359999Z

realized? is not a very useful predicate.

➕ 1
borkdude 2025-12-04T20:03:43.507119Z

public Obj withMeta(IPersistentMap meta){
	if(meta() == meta)
		return this;
	return new LazySeq(meta, seq());
}
So withMeta calls seq on the lazyseq, which forces the first result or chunk

Steven Lombardi 2025-12-04T20:04:37.571339Z

These are good points. I did not encounter this doing "normal" dev work. I hit this while working on my tracing interpreter. This is only an issue for me because I want my interpreted realization to match Clojure's.

Steven Lombardi 2025-12-04T20:06:08.813639Z

Thanks Dan.

Steven Lombardi 2025-12-04T20:06:50.430879Z

What I'm doing in my interpreter is just wrapping values that I don't want "accidentally" realized (either by adding meta or outputting to the repl or console). https://github.com/Lambeaux/paper-trail/blob/main/src/lambeaux/paper_trail/impl/executor/call_stack.clj#L20-L25

Steven Lombardi 2025-12-04T20:08:00.958529Z

It's been working well so far.

Steven Lombardi 2025-12-04T20:08:26.202569Z

But just so we're clear, @dpsutton we agree this is not, per Clojure's core team, considered a "bug"?

dpsutton 2025-12-04T20:08:57.041219Z

it’s been a known behavior for a while. my sense is that preserving laziness, going one at a time, not realizing the first chunk, are never considered contract behavior

2025-12-04T20:09:47.207299Z

there are asks about this behavior, i'd have to get on my computer to find them

borkdude 2025-12-04T20:11:42.307949Z

if you want to know the core team's stance on this, there's #clojure-dev or poke Alex

borkdude 2025-12-04T20:12:03.100249Z

(by now I'm pretty much convinced this is normal behavior, not bug)

seancorfield 2025-12-04T20:13:24.398249Z

If you consider this a bug, what aspect of the behavior do you think is incorrect? Genuinely curious.

Steven Lombardi 2025-12-04T20:13:29.164879Z

Lol. Well hang on. Dan, that slack link you posted where Alex says "no cases I would used realized? with lazy seq". THAT case seems more like a bug. At minimum wouldn't an infinite lazy seq implelment IPending but just always return false? I guess ... shoot ... that's a contract violation of sorts because in this thread realized? is returning true but that's JUST the first chunk.

Steven Lombardi 2025-12-04T20:14:05.223029Z

Yeah this seems like a can of worms I don't want to open right now...

seancorfield 2025-12-04T20:15:02.085429Z

But a lazy seq with the first element or chunk realized is... realized... kinda by definition of what realized? "means" (or clojure.lang.IPending/.isRealized).

borkdude 2025-12-04T20:15:42.519269Z

I just realized that 🥁 (sorry)

🤣 2
Steven Lombardi 2025-12-04T20:16:23.742149Z

> If you consider this a bug, what aspect of the behavior do you think is incorrect? Honestly, no idea at this point. I would need to think on it some more. Given the rant I just went on above.

2025-12-04T20:16:30.396999Z

depends on how you think of "realized". if you mean "has fully produced a value and cannot produce more" (like promise or delay), then you can't realize a lazy sequence (or it should always return false)

dpsutton 2025-12-04T20:16:53.360699Z

from the messages i’ve seen in the past, it seems more like a not-very-useful predicate on seqs than buggy

☝️ 1
1
2
borkdude 2025-12-04T20:17:41.715819Z

as often is the case, one should take a look at the docstring, not go by the intuition the name gives you.

Returns true if a value has been produced for a promise, delay, future or lazy sequence.
"a value"

👍 3
Steven Lombardi 2025-12-04T20:18:31.491859Z

And, the real question. What reasons in practice is it useful to know if a seq is realized, in part or in full? Why would someone want to know that? I can't think of a use case, to be honest.

Steven Lombardi 2025-12-04T20:18:53.397609Z

Now, is a seq infinite? That might be useful to know. I can think of a few cases for that.

dpsutton 2025-12-04T20:19:04.612349Z

it’s more like “hasNext” than “all values” on a seq

dpsutton 2025-12-04T20:19:18.256409Z

probably can’t know in general if a seq is infinite

seancorfield 2025-12-04T20:19:35.353289Z

More examples that might seem counter-intuitive:

user=> (realized? [])
Execution error (ClassCastException) at user/eval19718 (REPL:1).
class clojure.lang.PersistentVector cannot be cast to class clojure.lang.IPending (clojure.lang.PersistentVector and clojure.lang.IPending are in unnamed module of loader 'app')
user=> (realized? ())
Execution error (ClassCastException) at user/eval19720 (REPL:1).
class clojure.lang.PersistentList$EmptyList cannot be cast to class clojure.lang.IPending (clojure.lang.PersistentList$EmptyList and clojure.lang.IPending are in unnamed module of loader 'app')
user=> (realized? (list))
Execution error (ClassCastException) at user/eval19722 (REPL:1).
class clojure.lang.PersistentList$EmptyList cannot be cast to class clojure.lang.IPending (clojure.lang.PersistentList$EmptyList and clojure.lang.IPending are in unnamed module of loader 'app')
user=> (realized? (map inc ()))
false

dpsutton 2025-12-04T20:19:38.707839Z

(filter prime? (range))

seancorfield 2025-12-04T20:21:45.528199Z

FWIW, in our 150k line codebase at work, we use realized? just once, in a test, to check whether a promise got deliverd.

💯 1
Steven Lombardi 2025-12-04T20:22:44.391389Z

Re: Dan -- couldn't you propagate a separate property from (range) to (filter prime? *1)?

Steven Lombardi 2025-12-04T20:22:52.545269Z

Re: Sean -- thank you for the data point.

dpsutton 2025-12-04T20:23:08.985399Z

you’d have to encode whether that are infinite primes somewhere

😆 1
dpsutton 2025-12-04T20:23:16.518319Z

we certainly don’t do that currently

dpsutton 2025-12-04T20:23:46.968719Z

(filter #(< % 10) (range)) vs (filter prime? (range))

Steven Lombardi 2025-12-04T20:24:26.851429Z

With the exception of a finite take isn't all processing of infinite seqs also infinite themselves?

Steven Lombardi 2025-12-04T20:24:51.137849Z

Or, I guess, "effectively" infinite?

dpsutton 2025-12-04T20:24:52.218239Z

my example above of #(< % 10) is bounded

Steven Lombardi 2025-12-04T20:25:15.946489Z

Oh. Yup. My bad, you are correct. Thanks.

Steven Lombardi 2025-12-04T20:25:43.597909Z

Yeah that's non-trivial to infer.

dpsutton 2025-12-04T20:25:49.727909Z

but you brought up a good point. (take 30 (filter #(< % 10) (range)) will spin forever

Steven Lombardi 2025-12-04T20:28:25.265519Z

Oh shoot, yeah, it's an issue in BOTH directions. Functions that make an infinite seq no longer infinite, and taking from a finite seq but never getting enough elements.

phronmophobic 2025-12-04T20:29:04.694069Z

you can also create infinitely empty lazy sequences which are a pain for dev tools:

(remove (constantly true) (range))
Don't try this at home!

Steven Lombardi 2025-12-04T20:29:38.336469Z

Ugh. I knew my gut was warning me about this. > Yeah this seems like a can of worms I don't want to open right now...

Steven Lombardi 2025-12-04T20:30:09.864099Z

Okay I'm pivoting back to what I was doing and not opening a bug about this. lol.

😆 1
Steven Lombardi 2025-12-04T20:30:34.598609Z

Thank you, friends, for the discussion. I'm sure I'll refer back to this thread in the future.

exitsandman 2025-12-04T21:02:57.710069Z

TIL lazy seqs are valid targets for realized? in the first place. I've always understood it in relation to delay and such

seancorfield 2025-12-04T21:05:04.685459Z

...for some weird definition of "valid" there, I guess 🙂

seancorfield 2025-12-04T21:05:30.784379Z

(not= :valid :useful) 🤣

seancorfield 2025-12-13T17:19:46.426609Z

user=> (def x (map inc (range)))
#'user/x
user=> (realized? x)
false
user=> (realized? (rest x))
false
user=> (realized? x)
true
user=> (realized? (next x))
Execution error (ClassCastException) at user/eval19770 (REPL:1).
class clojure.lang.Cons cannot be cast to class clojure.lang.IPending (clojure.lang.Cons and clojure.lang.IPending are in unnamed module of loader 'app')
That last one surprised me but it shows that while rest causes the first element to be realized, the "rest" of the seq is still, indeed, unrealized.

2025-12-13T17:21:14.984929Z

Cons isn't IPending? interesting

2025-12-13T17:24:56.264869Z

but i'm also surprised realized? doesn't work on all types

seancorfield 2025-12-13T17:29:19.139819Z

(defn realized?
  "Returns true if a value has been produced for a promise, delay, future or lazy sequence."
  {:added "1.3"}
  [^clojure.lang.IPending x] (.isRealized x))

seancorfield 2025-12-13T17:31:43.242429Z

Also, the only reason the above actually worked is because of (range):

user=> (def x (map inc [0 1 2 3 4 5 6 7]))
#'user/x
user=> (realized? x)
false
user=> (realized? (rest x))
Execution error (ClassCastException) at user/eval19783 (REPL:1).
class clojure.lang.ChunkedCons cannot be cast to class clojure.lang.IPending (clojure.lang.ChunkedCons and clojure.lang.IPending are in unnamed module of loader 'app')
So you can't use it on chunked lazy sequences in general (re: @frankleonrose’s comment about optimizing the use of take).

1
2025-12-13T17:35:03.440419Z

given the pervasive use of cons to create lazy sequences, realized? seems like a pretty good footgun

seancorfield 2025-12-13T17:37:21.587309Z

It's fine for delay/promise/future. It's useless for sequences in general. It's... weird... for the very specific case of certain lazy sequences 🙂

2025-12-13T17:37:52.044409Z

lol exactly my point

Steven Lombardi 2025-12-13T19:01:36.280959Z

I just rolled my own flavor of "realized" to suit my specific needs.

Steven Lombardi 2025-12-13T19:02:09.248459Z

But my needs are, unique, to say the least.

dpsutton 2025-12-13T19:02:41.086089Z

what did you do? also interested in your use case.

dpsutton 2025-12-13T19:02:51.022559Z

(for learning, not for arguing 🙂 )

❤️ 1
Steven Lombardi 2025-12-13T19:13:01.079939Z

But Dan, if you want to have a friendly argument for a bit, that's fine. Sometimes we need to argue to learn. I can share more specifics if you're curious.

Steven Lombardi 2025-12-13T19:13:47.561109Z

For example, in my case, the wrong arg to println for logging purposes will fail tests due to premature realization.

dpsutton 2025-12-13T19:15:48.163699Z

i think it can get exhausting if you ask a question and everyone tries to say that you shouldn’t do whatever you are doing. I am very interested in what you doing and then how you accomplish it and didn’t want you to think i would push back on your decisions and such. I’ve heard it’s one of the social conventions that made it super annoying to ask questions on stack overflow and such

➕ 2
Steven Lombardi 2025-12-13T19:36:05.446649Z

I think you are 100% correct. I might have a different perspective because with teams I've led in the past, I've seen the "no man" attitude fizzle when I have one on one discussion. That's something you definitely don't get on SO though, so I appreciate where you're coming from.

👍 1
Steven Lombardi 2025-12-13T19:38:52.956269Z

I appreciate you not wanting to discourage me.

seancorfield 2025-12-13T20:16:25.407199Z

I think the consensus here is: whatever you're trying to build is fine, but realized? is not going to be useful for that 🙂 I think Paper Trail is a fascinating and worthwhile project but it is definitely... "out there"...

👍 1
frankleonrose 2025-12-13T03:59:38.215099Z

> What reasons in practice is it useful to know if a seq is realized, in part or in full? In tuned context you might want to take only when you know you won't incur cost of realizing a chunk. Which leads to the question, after first chunk is consumed, would realized? then return false. AFK or I’d check.