[Openmcl-devel] Format bug?

Gary Byers gb at clozure.com
Fri Jan 6 15:25:18 PST 2012




On Fri, 6 Jan 2012, Waldek Hebisch wrote:

> Gary Byers wrote:
>>
>>
>>
>> On Thu, 5 Jan 2012, Waldek Hebisch wrote:
>>
>>> $ scripts/ccl64
>>> Welcome to Clozure Common Lisp Version 1.8-dev-r15159M-trunk  (LinuxX8664)!
>>> ? (setf *print-circle* t)
>>> T
>>> ? (setf ll '(#1="PrimeField" #2="(" #1# #2# "x" ")" ")"))
>>> (#1="PrimeField" #2="(" #1# #2# "x" ")" ")")
>>> ? (FORMAT nil "~{~A~}" ll)
>>> "#1=PrimeField#2=(#1##2#x))"
>>> ?
>>>
>>> HypeSpec says that ~A should just use string "as is"
>>
>> It does ?
>>
>> The spec says a few things about ~A (and about PRINC), but none of
>> them seem to use the term "as is."  It does say that ~A will print its
>> argument without escape characters (and that it binds *PRINT-ESCAPE*
>> and *PRINT-READABLY* to NIL), and that certain modifiers control how
>> NIL is printed and control justifcation and padding.  None of this
>> is defined to have any effect on whether or how *PRINT-CIRCLE* introduces
>> #n=/#n# syntax.
>
> From HyperSpec:
>
> : 22.3.4.1 Tilde A: Aesthetic
> :
> : An arg, any object, is printed without escape characters (as by princ).
> : If arg is a string, its characters will be output verbatim.

Without escape characters (surrounding  quotes.)  That's
not what we're talking about here.  Implementations (likely) don't
differ in their treatment of (FORMAT NIL "~A" LL) in your example, but
they do differ in their handling of (FORMAT NIL "~{~A~}".  This has
nothing to do with ~A, and it's not useful to continue to think that
it does.

>
>> The spec also says quite a bit about how *PRINT-CIRCLE* affects
>> printing; none of what it says indicates that *PRINT-ESCAPE* or
>> *PRINT-READABLY affect what *PRINT-CIRCLE* does.  Unfortunately, some
>> aspects of what *PRINT-CIRCLE* does - notably, the "extent" over which
>> circularity/sharing detection is performed - are left unstated, and
>> implementations differ in this regard.
>>
>
> Well, looking at the letter of HyperSpec you are right.

Hopefully, this means that we're past thinking that this has something
to do with ~A.

> But
> the intent of sharing detection (as opposed to merely detectiong
> circularity) is print/read equivalence.  This looses sense when
> printing separate objects.  So doing circularity detection
> for each toplevel object separately seems to match the intent.

Where is this intent stated, exactly ?

(Of course, by binding *PRINT-ESCAPE* and *PRINT-READABLY* to NIL,
~A already defeats print/read equivalence in many cases, but I'd
agree that doing circularity/sharing detection at a different granularity
than CCL does could be useful.  I believe that doing it at the level
that CCL currently does can also be useful, and I don't think that the
spec says that either of these behaviors is more or less correct than
the other.)

Because the spec isn't clear in this case, people can infer its intent
differently.  There's nothing at all wrong with your model, but there's
nothing at all right about it, either.

Suppose a user-defined PRINT-OBJECT method wants to print the values
of a couple of an instance's slots.  There could plausibly be good reasons
to want to know if those values share structure:

(defmethod print-object ((instance some-class) stream)
   (print-unreadable-object (instance stream :type t :identity t)
     (with-slots (a b) instance
        (let* ((*print-circle* t))
           (format stream "a=~s b=~s" a b))))) ; ~s vs ~a makes no difference

The author of such a method can't portably expect binding
*PRINT-CIRCLE* to do sharing detection for them, though it might.  If
it doesn't do so, they could protest to the responsible parties that
their expectation was reasonable, that nothing in the spec contradicts
it, and might even say that "the intent of *PRINT-CIRCLE* was clearly
to force the implementation to do sharing detection so that the user
doesn't have to."  The responsible parties might respond by saying
"Where is this intent stated, exactly ? ..."

Since the answer to that question is "nowhere", implementations differ
significantly in their handling of *PRINT-CIRCLE* and have done so for
many, many years.  (I don't know, but I wouldn't be shocked if
implementations that generated the same output on your test case
generated different output in other cases.)  That's an unfortunate
situation, and it leads to cases where people write code that
(consciously or otherwise) depends on non-portable behavior.

Trying to avoid that non-portable behavior (and yes, that's simpler
in some cases than others) is probably a better strategy than saying
"break the other guy's code so that mine will work as I expect it to",
since that just leads to The Other Guy saying the same thing a little
later.

Long-standing behavior can be (and has been) clearly wrong, and user code
that depends on something that's wrong is already broken and should be changed.
In this case, I don't think that there's a clear enough definition of what's
right and wrong to justify changing CCL (and possibly breaking user code that's
depended on its current behavior.)

This issue does come up from time to time.  The last time that I remember it
doing so, the person that reported it thought that it had something to do
with ~A, and continued to believe that after other interested parties recognized
that it didn't.  Hopefully, that's no longer the case here, but it's possible
to form a model of things ("~A shouldn't do circularity/sharing detection because
the results wouldn't be aesthetic.") that's consistent, useful, and completely
wrong.

If you've gotten past that, you're now advocating a model that says
that *PRINT-CIRCLE* should only take effect when it supports an
informal notion of read/print consistency.  That model is consistent,
useful, and not in any way that I know of wrong, but the same can be
said for other contradictory models.

(I haven't even looked at the code and don't know how practical it'd
be, but if we're mostly talking about FORMAT's behavior when multiple
objects are printed and *PRINT-CIRCLE* is in effect, it might be
possible to control that behavior via a global variable -
*FORMAT-CIRCULARITY-DETECTION-SOMETHING-OR-OTHER* - and could then
spend way too much time arguing about what the default value of that
variable should be.  If that's practical, it's more palatable to me than
unconditionally breaking any user code that depends on the current
behavior would be.)





>
> --
>                              Waldek Hebisch
> hebisch at math.uni.wroc.pl
>
>



More information about the Openmcl-devel mailing list