On Tue, 17 Dec 2002, Marc Feeley wrote: >This is not at all my perspective. I manipulate large cyclic data >structures all the time (doubly-linked lists, trees with >back-pointers, object system class descriptors, etc), my programs >(sometimes!) have bugs and I want to dump these cyclic data-structures >nicely so that I can figure out what the problem is. I do this all >the time! It would be a shame if I could not combine the >pretty-printing feature with the shared data printing feature. My >point is that these features are not mutually exclusive, so the API >should be designed to let the user pick the features needed. Okay.... you've convinced me that there needs to be some kind of extensible mechanism for specifying options. > - Should hidden fields of an object be written or not? This is > useful for debugging but also for saving an object to a file and > reading it back in later. This is immaterial since "hidden fields" are a completely undefined concept in R5RS and, AFIR, also undefined in all existing Scheme standards and SRFI's. I'm not convinced that hidden fields as such are a good idea, and if anyone needs them they can be implemented with standard scheme data structures such as pairs, vectors, etc. I'm not going to piggyback definitions of entirely new, or implementation- dependent, language features onto this SRFI. > - Should numbers that are eq? be marked as shared or not? For > example in (123 . 123) both numbers are probably eq?, and perhaps > also in (0.0 . 0.0). Well, my intent was to recover (eq?) relationships when reading anything that had been written. And at first glance I was prepared to simply answer, "no, numbers are (eq?) whenever they are (eqv?), so this is a distinction that makes no difference." However, that was over hasty, because R5RS states: > Eq? and eqv? are guaranteed to have the same behavior on symbols, > booleans, the empty list, pairs, procedures, and non-empty > strings and vectors. Eq?'s behavior on numbers and characters is > implementation- dependent, but it will always return either true > or false, and will return true only when eqv? would also return > true. Eq? may also behave differently from eqv? on empty vectors > and empty strings. So it appears that there may be situations in which numbers may be (eqv?) without being (eq?) in some schemes. (What scheme are you using, marc? I've never had this happen and didn't know it was possible!) So at second glance it would look like this would gum up the works badly, because if I, in an implementation where numbers are (eq?) whenever they are (eqv?), write a structure and marc, in an implementation where numbers have individual identities as well as values, reads a structure, odds are the structure I wrote is not going to be the same as the structure he reads regardless of the behavior of our (write-showing-shared) function. And vice versa, of course. But at third glance, (eq?) objects behave just like (eqv?) objects in the absence of assignment - and there is no way in R5RS to assign to a number or a character without changing its identity and thereby terminating the (eq?) relationship -- without a side effect on whatever it formerly had an (eq?) relationship with. In an implementation where (eqv?) numbers can be non-(eq?), they are guaranteed to be non-(eq?) after an assignment to one of them, and the other will not be changed by that assignment. This is just as though they had been non-(eq?). And this is also true of characters. Numbers and characters are the lowest-level entities, not mutable containers for values. So the effects of mutation on numbers or characters that are or aren't (eq?) will never make a difference to normal program semantics. So this must be why the R5RS authors chose to allow implementation-dependent behavior on exactly those two classes of values; because under normal program semantics the distinction can never matter. And at fourth glance, we must take into account abnormal program semantics. Of course, those are the ones that are specifically relying on the function (eq?) itself -- and this is unavoidable because (eq?) itself has different semantics in our two implementations and our programs would have behaved differently even on the identical original structure! Aargh! What we have here is a clear case of trying to provide stable, portable infrastructure and discovering that infrastructure necessary to support it and beyond the scope of what we are trying to provide here, is neither completely stable or portable. Crap. I still believe that (write-showing-shared) is more portable and reliable than (write), but clearly the Right Thing To Do is forced to also be implementation-dependent, and thus of limited portability, here; on schemes that allow non-(eq?) numbers and characters to be (eqv?) it should show (eq?) relationships among numbers and characters, and in schemes where all (eqv?) numbers and characters are also (eq?), it should not. > - Should the low-level code of procedures (bytecode, or whatever) be > dumped? Same issue for continuations. Again, implementation-dependent. (Write-showing-shared) must produce an external representation for anything that has an external representation under (write), and those are entities that may or may not have an external representation under your particular implementation's (write) and (read) functions. If this SRFI mandates implementors coming up with a portable notation for the low-level code of comiled routines, just watch how fast they scramble to ignore it. I'd like to have that, sure, but it's unreasonable - and also not easily or portably implementable. > - Should the object be written as text or in binary? After all if a > human is not going to read the data using text is just wasteful. > A binary representation, or even a special purpose textual > representation would be more compact. Text is now and has always been more portable across systems. And human readability, while not the purpose of a structure write, has benefits in debugging and other places as you've pointed out. >So in the end the "write-showing-shared" procedure still needs >parameters. I'm considering it, but I'm far from convinced. > We don't yet know how many we need (and probably never >will as the need for new features never ceases to grow). And this makes me exceedingly loath to include and define them at this point. > This is why >an API based on dynamically bound parameter objects is needed. >Alternatively, explicit keyword parameters could also be used: No. Dynamically-scoped parameters and keyword parameters are pure mud in a lexically-scoped prefix-notation language. I will not recommend them because they are ugly and introduce complications and dependencies. What you want to pass in, *IF* you insist on parameterizing the call at all, is probably a thunk. > (write obj port shared: #t pretty: #t) Without introducing any strange unportable calling conventions or syntax definitions, you could say this; (let ((writetable (lambda (arg) (case (arg) ((prettyprint) #t) ((show-shared) #t) (else #f)))) (port standard-output-port)) (write port writetable)) You can dress it up with syntax any way you want, but the above is the basic structure of the call you want, and probably what I'd write because I don't like keeping bunches of syntax definitions in my head. But I persist in saying that while leaving room for a prettyprint extension isn't a bad idea, providing the prettyprint extension, or any other beyond showing shared structure, is absolutely not what this is about. >I have also found readtables to be a good way to package up the >parameters that are used by "write" and "read". The readtable >specifies the external representation and "write" and "read" use >the readtable when generating the external representation or >when parsing an object. Right, the same idea as the thunk I assigned to the variable named 'writetable' above. But I would avoid calling it readtable; that name is in use for a different concept by Common Lisp. Bear