Switch to write-unreadable? Lassi Kortela (17 Dec 2022 13:40 UTC)
Re: Switch to write-unreadable? John Cowan (18 Dec 2022 07:33 UTC)
Re: Switch to write-unreadable? Lassi Kortela (21 Dec 2022 11:39 UTC)
Re: Switch to write-unreadable? Marc Feeley (18 Dec 2022 14:16 UTC)
Re: Switch to write-unreadable? Lassi Kortela (21 Dec 2022 11:32 UTC)

Re: Switch to write-unreadable? Marc Feeley 18 Dec 2022 14:16 UTC

> On Dec 17, 2022, at 8:40 AM, Lassi Kortela <xxxxxx@lassi.io> wrote:
>
> I tried to play around with the #< syntax some more. Still can't find anything that looks good and could be parsed reliably. It makes sense for RnRS to cause a syntax error upon encountering #< but beyond that, there isn't anything to say about it. I can't dedicate a whole SRFI to saying one sentence.
>
> Given that Shiro and I are the only ones so far who like the proposed #?datum syntax, I'll hold off on proposing it in this SRFI and retarget the SRFI to specify a `write-unreadable` procedure instead.
>
> The idea is that (write-unreadable list [port]) writes a representation of an unreadable object to the given port. It's given a list with one or more items. The first item is a symbol indicating what type of object is being represented; the rest can be arbitrary objects giving extra detail.
>
> The lexical syntax to write is implementation-defined; can be #<foo ...>, #[foo ...], #?(foo ...), or something else.
>
> In the future, some Scheme implementations will probably be able to write more than one kind of syntax. Syntax settings should probably be associated with port objects. So (write foo some-port) and (write-unreadable foo some-port) would both use the appropriate syntax for some-port.
>
> Does this sound reasonable, given the circumstances, or should I do something else?

I think that specifying a write-unreadable procedure is the wrong way to achieve the goal of writing unreadable data.  The problem is that unreadable data may be nested at arbitrary depth in a container object that is written.  With write-unreadable it is necessary to implement a custom “write” procedure that walks the object to be written and calls write-unreadable when it detects that an unreadable data is encountered.  This is not ideal because any system specific feature of the Scheme system’s “write” procedure will no longer be available.

An approach which avoids this problem is based on read-macro “inversion” by the write procedure.  By this I mean detecting lists that begin with the symbols “quote”, “unquote”, etc and outputing the more compact read-macro form instead of the normal list form.  Here’s an example with Gambit:

> (begin (write (list 11 (list 'quote (list 1 2 3)) 22)) (newline))
(11 '(1 2 3) 22)
> (begin (write (list 11 (list 'unquote (list 1 2 3)) 22)) (newline))
(11 ,(1 2 3) 22)
> (begin (write (list 11 (list 'unquote-splicing (list 1 2 3)) 22)) (newline))
(11 ,@(1 2 3) 22)

I’m not sure how widely read-macro inversion is implemented for the write procedure, but it is typically implemented by pretty printing procedures.  Here are examples with Guile and Chicken:

xxxxxx@(guile-user)> (use-modules (ice-9 pretty-print))
xxxxxx@(guile-user)> (pretty-print (list 11 (list 'quote (list 1 2 3)) 22))
(11 '(1 2 3) 22)

#;1> (import (chicken pretty-print))
; loading /usr/local/Cellar/chicken/5.2.0/lib/chicken/11/chicken.pretty-print.import.so ...
#;2> (pretty-print (list 11 (list 'quote (list 1 2 3)) 22))
(11 '(1 2 3) 22)

Gambit has extended this idea to other lists of the form (<special-symbol> ...) .  For example when <special-symbol> is the symbol |{...}| then the write procedure will wrap the list elements in braces:

> (begin (write (list 11 (list '|{...}| 1 2 3) 22)) (newline))
(11 {1 2 3} 22)

This also works for |[...]|, which is useful to force the use of square brackets for specific sublists:

> (begin (write (list 11 (list '|[...]| 1 2 3) 22)) (newline))
(11 [1 2 3] 22)

Note also that by default Gambit does not implement the R6RS equivalence (…) <-> […] but rather the reader will build the list (|[...]| a b c) when it reads [a b c]:

> '[1 2 3]
[1 2 3]
> (cons '==> '[1 2 3])
(==> |[...]| 1 2 3)

This allows the programmer to give a specific meaning to the {a b c} and [a b c] forms, for example:

> (define |[...]| vector)
> [1 2 3]
#(1 2 3)
> (define [x y] (if (pair? x) (cons (car x) [(cdr x) y]) y)) ;; append
> ['(1 2 3) '(4 5 6)]
(1 2 3 4 5 6)

Note that this is similar to doing this, which is standard Scheme code:

> (define ,n (if (= n 0) 1 (* n ,(- n 1)))) ;; factorial
> (number->string ,20)
"2432902008176640000"

Now that I have explained that background material it should be clear how this can be used for writing unreadable data by using the |#<...>| special symbol at the head of a list:

> (begin (write (list 11 (list '|#<...>| (list 'foo 'bar)) 22)) (newline))
(11 #<(foo bar)> 22)

So I think a better path would be to start by specifying a read-macro inversion SRFI for the standard R7RS read-macros.  Then another SRFI could specify an extension for writing unreadable data as in the above example using the |#<...>| special symbol.  Another SRFI could also specify the case of |[...]| and |{...}|.

Marc