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.
is the correct way to call seq inside a try/catch?
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?
@jimka.issy seqable? should suffice in your case - https://github.com/clojure/clojure/blob/e6393a4063c42ddc0e0812f04464467764f0fd1e/src/clj/clojure/core.clj#L6281
@bg, in my opinion this she be mentioned on https://clojure.org/reference/sequences no?
@jimka.issy agreed... @alexmiller is the best person to decide on this matter.
You can file an issue at https://github.com/clojure/clojure-site/issues
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
in general it's very rare to check
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
@alexmiller wow, thanks. that’s great
@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.
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)))))brittle-equal
Watch out, that'll fail in some strange and annoying ways
tell me.
(= (type (list 1 2 3)) (type (take 3 (list 1 2 3))))
;; falsewhere two sequences are equal only if their types are[1 2 3] is not a sequence (it's sequential though)
(= (type {:a 1 :b 2}) (type (zipmap (range 10) (range 10))))
;; false
all of this is mechanical anyhow... why are you making this fn in the first place?
yes [1 2 3] is not a sequence, but = thinks it is equal to (1 2 3)
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
@noah, those are good examples. yes I want those to be considered unequal.
at least for my experimental application.
i think you'll find that clojure is really annoying and won't work great if you go this route
for example:
(= (type (zipmap (range 8) (range 8)))
(type (zipmap (range 9) (range 9))))
;; falseoh 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.
map literals with 8 or fewer entries use PersistentArrayMap, maps with more than 8 entries use PersistentHashMap
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)))
;; falseas you can see, when you shrink a PersistentHashMap, it isn't moved back to a PersistentArrayMap
or at least it's undefined what the concrete class of a "map-like" object in clojure will be
so maybe some internal function will convert to a PAM, while another won't. The interface is the only thing that matters
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.
when manipulating data which is code, it is dangerous to consider those as being equal.
(fn [x] x) is not checked with =, it's checked with vector?
(fn [x] x) is not a vector
is it?
Noah meant the [x] bit.
Check out (source fn).
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.
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)))
that is pretty common to write very complicated code to detect that things are not really equal which = thinks are equal.
clojure is generally not interested in the concrete type of a given object
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.
> 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.
> 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.
you've not said the goal of these questions. is this for your class to check their homework?
we might be in an "x y" situation where we're answering your question but not giving you a nicer solution to your problem
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.
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.
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"
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.
I haven’t actually found any academic papers on malli. Does anyone know whether such exists?
i suspect not
You've asked that before around half a year, maybe a year ago. :) I doubt the answer has changed.
@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.
In the end if my research topic proves useless, I will have learned a lot in trying to understand why it won’t work.
there's https://github.com/pangloss/pattern/ for general sexp pattern matching, which might be helpful in your search
@Noah, Thanks for the reference. I have the book that software is based on, but I never finished reading it.
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))
trueNot an issue for me. I'm just wrapping my seqs in my own IObj to avoid this behavior where necessary.
@lambeauxworks did you submit this as a bug to CLJS?
oh wait, it's JVM Clojure?
I misread clj꞉user as cljs.user which is the name of the default CLJS namespace :)
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".
seems like a bug to me
i’ve always heard the degree of laziness is not anything promised by Clojure
I agree it seems like a bug in that it strikes me as surprising behavior. But I also have heard the same, Dan.
ah it seems only the first chunk is realized
yes realized doesn’t mean the entire sequence right?
Pretty sure that is the case, but I can verify...
(let [s (with-meta (map inc (range)) {:infinite true})]
(realized? s))
trueinfinite sequence is a good example
oh doesn't it mean that? that's even more confusing to me
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
and that if you are fighting to keep a particular amount of laziness you are off the happy path
realized? is not a very useful predicate.
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 chunkThese 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.
Thanks Dan.
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
It's been working well so far.
But just so we're clear, @dpsutton we agree this is not, per Clojure's core team, considered a "bug"?
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
there are asks about this behavior, i'd have to get on my computer to find them
if you want to know the core team's stance on this, there's #clojure-dev or poke Alex
(by now I'm pretty much convinced this is normal behavior, not bug)
If you consider this a bug, what aspect of the behavior do you think is incorrect? Genuinely curious.
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.
Yeah this seems like a can of worms I don't want to open right now...
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).
I just realized that 🥁 (sorry)
> 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.
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)
from the messages i’ve seen in the past, it seems more like a not-very-useful predicate on seqs than buggy
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"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.
Now, is a seq infinite? That might be useful to know. I can think of a few cases for that.
it’s more like “hasNext” than “all values” on a seq
probably can’t know in general if a seq is infinite
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(filter prime? (range))
FWIW, in our 150k line codebase at work, we use realized? just once, in a test, to check whether a promise got deliverd.
Re: Dan -- couldn't you propagate a separate property from (range) to (filter prime? *1)?
Re: Sean -- thank you for the data point.
you’d have to encode whether that are infinite primes somewhere
we certainly don’t do that currently
(filter #(< % 10) (range)) vs (filter prime? (range))
With the exception of a finite take isn't all processing of infinite seqs also infinite themselves?
Or, I guess, "effectively" infinite?
my example above of #(< % 10) is bounded
Oh. Yup. My bad, you are correct. Thanks.
Yeah that's non-trivial to infer.
but you brought up a good point. (take 30 (filter #(< % 10) (range)) will spin forever
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.
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!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...
Okay I'm pivoting back to what I was doing and not opening a bug about this. lol.
Thank you, friends, for the discussion. I'm sure I'll refer back to this thread in the future.
TIL lazy seqs are valid targets for realized? in the first place. I've always understood it in relation to delay and such
...for some weird definition of "valid" there, I guess 🙂
(not= :valid :useful) 🤣
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.Cons isn't IPending? interesting
but i'm also surprised realized? doesn't work on all types
(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))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).given the pervasive use of cons to create lazy sequences, realized? seems like a pretty good footgun
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 🙂
lol exactly my point
I just rolled my own flavor of "realized" to suit my specific needs.
But my needs are, unique, to say the least.
what did you do? also interested in your use case.
(for learning, not for arguing 🙂 )
Have a look at this namespace. https://github.com/Lambeaux/paper-trail/blob/main/src%2Flambeaux%2Fpaper_trail%2Fimpl%2Fexecutor%2Fboxed_vals.clj
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.
For example, in my case, the wrong arg to println for logging purposes will fail tests due to premature realization.
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
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.
I appreciate you not wanting to discourage me.
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"...
> 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.