How to safely print structures that may contain infinite lazy seqs?

148 views
Skip to first unread message

Austin Haas

unread,
Nov 1, 2020, 6:06:39 PM11/1/20
to Clojure

How can I make sure that a logging function won't try to realize an infinite lazy seq that could be anywhere in the arguments passed to the logging function?

Is there some way to guarantee that lazy seqs won't be realized when converting to a string?

I know I can bind *print-length*, but I don't want to constrain every collection.

And I know that lazy seqs aren't always realized, but that doesn't seem to help if they are infinite:

user=> (str (map inc (range 10)))
"clojure.lang.LazySeq@c5d38b66"

user=> (str (map inc (range)))
<never ends>

Thanks.

Juan Monetta

unread,
Nov 2, 2020, 12:22:58 PM11/2/20
to Clojure
Hi Austin,

Since there is no way to know the length of a lazy-seq without realizing it, I think your only choice is to set a limit on it by binding *print-length* if you are not sure about the sequence. 

Other thing you can try is bounded-count like this :

(defn looks-finite? [xs]
  (let [limit 1000]
    (< (bounded-count limit xs) limit)))

(looks-finite? (map inc (range))) ;; => false
(looks-finite? (map inc (range 100))) ;; => true

I hope that helps.

Juan

Austin Haas

unread,
Nov 2, 2020, 3:31:53 PM11/2/20
to Clojure
Thanks, Juan.

I don't need to know the length of the seq, though, only that it is lazy. I don't want to realize any lazy seqs. Ideally, I'd like to be able to print any data structure and have all lazy seqs print just like it does in the example I gave above (i.e., "clojure.lang.LazySeq@c5d38b66"), whether it is finite or infinite.

I also don't want to walk through every data structure to check if it contains a lazy seq, but maybe that is the only option.

I've also tried:

(defmethod print-method clojure.lang.LazySeq [q, w]
     (.write w "#clojure.lang.LazySeq"))

The next step might be to investigate why infinite lazy seqs don't print as clojure.lang.LazySeq, like the finite ones.

Justin Smith

unread,
Nov 2, 2020, 3:37:00 PM11/2/20
to Clojure
> The next step might be to investigate why infinite lazy seqs don't print as clojure.lang.LazySeq, like the finite ones.

that printing of "clojure.lang.LazySeq@c5d38b66" relies on completely
realizing the input, as it relies on the hash, which relies on the
fully realized value
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/ab820e20-75ad-4852-aa01-9321cb7487b4n%40googlegroups.com.

Justin Smith

unread,
Nov 2, 2020, 3:40:48 PM11/2/20
to Clojure
hit send too soon --

also, that print-method doesn't catch all lazy values

user=> (instance? clojure.lang.LazySeq (range))
false

user=> (supers (class (range)))
#{java.lang.Iterable java.util.List clojure.lang.Obj
clojure.lang.IPending java.io.Serializable clojure.lang.IHashEq
java.util.Collection clojure.lang.IObj clojure.lang.Sequential
clojure.lang.Seqable clojure.lang.IPersistentCollection
clojure.lang.ASeq clojure.lang.IReduce java.lang.Object
clojure.lang.ISeq clojure.lang.IMeta clojure.lang.IReduceInit}

Austin Haas

unread,
Nov 2, 2020, 8:23:08 PM11/2/20
to Clojure

Thanks, Justin!

Yeah, I noticed that range doesn't return an instance of clojure.lang.LazySeq, so I added a print-method for clojure.lang.Iterate. And that one seems to work as expected, but apparently you can't override the print-method for clojure.lang.LazySeq.

But this doesn't seem like a good approach, anyway, because I don't want to change the printing behavior globally.

I think I'm just going to have to forego logging arbitrary things, and maybe implement some optional santization if necessary.

Bret

unread,
Nov 3, 2020, 10:22:54 PM11/3/20
to Clojure
I'm not 100% happy with this plus it doesn't really answer the original question, but in case it helps in some way ...

Recently, I had a record I was working on that holds a seq that could be a lazy infinite seq that I ended up using this as the major part of print-method implementation:

(def ^:dynamic *print-look-ahead-length* 2)

(defn seq->str
  [s]
    (let [[f' r] (split-at *print-look-ahead-length* s)
            f (apply list f')]
      (if (seq r)
           (str "(" (first f) " " (second f) " <+ more>)")
           (str f))))

I played with checking for instance of a number of things that just got too messy for me at the time. So, for now, I just decided it was good enough to basically see 2 items into the seq and indicate if there are more. This could easily be made more variable in places.

Bret

Michiel Borkent

unread,
Nov 7, 2020, 5:39:52 PM11/7/20
to Clojure
I wrote a little library for this purpose a while ago:

Reply all
Reply to author
Forward
0 new messages