Welcome! Please see the About page for a little more info on how this works.

0 votes
in Clojure by

Collection classes implement {{toString}} by delegating to {{RT.printString}}, which in turn is affected by the value of * }:

Clojure 1.9.0-beta1 user=> (binding [*print-readably* false] (str ["foo"])) "[foo]" user=> (binding [*print-readably* true] (str ["foo"])) "[\"foo\"]"

The attached patch fixes this by replacing the calls to {{RT.printString}} in collection {{toString}} implementations with calls to a new {{RT.prString}} method that explicitly binds * }.

See https://groups.google.com/d/msg/clojure/S13swxLy1ng/FKLYdY9HAgAJ for the original report of interactions between {{lazy-seq}}, {{print}} and {{str}} that are ultimately caused by the above issue.

7 Answers

0 votes
by

Comment made by: alexmiller

It's not clear to me why or whether the current behavior is wrong?

0 votes
by
_Comment made by: michalmarczyk_

I would expect {{str}} / {{toString}} to be stable when applied to persistent collections of immutable items. In other words, when applied to immutable inputs, I would expect it to be a pure function of the arguments explicitly passed in.

There is certainly no way for users to expect any dependency on any dynamic Vars here, or indeed anything other than the argument – which is a pure value.

The original example in the Google group thread is of a lazy seq that is ostensibly a value – (lazy-seq [(str ["a string"])]) – which returns a different {{pr-str}} (and {{print-method}}) representation depending on whether or not one passes it to {{print}} in between creating it and calling {{pr-str}} on it:


Clojure 1.9.0-beta1
user=> (let [x (lazy-seq [(str ["a string"])])] (print x) (pr-str x))
([a string])"(\"[a string]\")"
user=> (let [x (lazy-seq [(str ["a string"])])] (pr-str x))
"(\"[\\\"a string\\\"]\")"


Here the user might expect the {{pr-str}} call to be independent of the {{print}} call, since the former only takes place once the latter returns, and yet there is a spooky interaction.

The patch fixes this:


Clojure 1.9.0-master-SNAPSHOT
user=> (let [x (lazy-seq [(str ["a string"])])] (print x) (pr-str x))
(["a string"])"(\"[\\\"a string\\\"]\")"
user=> (let [x (lazy-seq [(str ["a string"])])] (pr-str x))
"(\"[\\\"a string\\\"]\")"
0 votes
by

Comment made by: alexmiller

This is perhaps a philosophical argument and I don't really have a definitive answer, but happy to kick it back and forth. :)

Agreed that the persistent collection of immutable values is a value. However, there are many ways to build a string representing a view of that immutable data - we have several built into the Clojure print system (pprint, pr, print) + a variety of knobs like collection size limits, etc. I don't see any principle that leads me to believe that the toString has to be independent from the knobs choosing that view.

In other words, I don't necessarily see this as a problem to be solved.

0 votes
by

Comment made by: notespin

I think it's true this is a little philosophical. However, to add some weight toward the side of the patch, I think toString and thus str is generally expected by programmers to be stable. So I would say the current behaviour breaks the principle of least surprise. I'd vote for making str/toString stable, that said, there is a very small possibility this chamge would be a breaking change to someone.

0 votes
by

Comment made by: alexmiller

As a grizzled Java veteran, I have 0 expectations about toStrings. Usually in Java they are built on mutable fields and are wildly unstable, so I certainly don't share your expectation. :)

str inherently involves "printing" (creating a string view of a value). I think str is "stable", just not solely a function of its explicit inputs (boo hidden state). To make an analogy, there are many ways to create a string from a date object and toString of a java.util.Date will format the string using your timezone, which is external hidden state.

I'm still not necessarily opposed to the changes here. I just don't find it to be obviously the right thing to do.

From a general perspective, I think the "principle of least surprise" implies much greater commonality in what people find surprising than actually exists, so I put little stock in that. I put a lot more weight in an argument that follows from some stated principles. I think this area is underdocumented/underspecified though.

0 votes
by

Comment made by: pbwolf

The original issue starts with {{toString}}. A more vivid problem is that the issue demonstrates that {{pr}} is undependable to emit EDN with.

Absence or presence of the intervening {{print-str}} (which the caller of {{pr}} might have nothing to do with) makes a material difference:

`
user> (clojure.edn/read-string

    (first
      (clojure.edn/read-string
        (let [mk-str (fn [] (lazy-seq [(str ["ZiZi"])]))
              a (mk-str)]
          ;(print-str a)
          (pr-str a)))))

["ZiZi"]

user> (clojure.edn/read-string

    (first
      (clojure.edn/read-string
        (let [mk-str (fn [] (lazy-seq [(str ["ZiZi"])]))
              a (mk-str)]
          (print-str a)
          (pr-str a)))))

[ZiZi]
`

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJ-2248 (reported by michalmarczyk)
...