Am Mi., 27. Okt. 2021 um 16:14 Uhr schrieb Marc Feeley - feeley at iro.umontreal.ca (via srfi-discuss list) <xxxxxx@srfi.schemers.org>:

> > The problem I have with eof-object in generator is that eof-object is not a disjoint type for a dijoint behavior such as described in null object pattern [0].
> > In a way that is similar to JavaScript's `undefined` that shows up in code or errors, without giving information about what is the actual context or problem.
>
> A solution to this is to (conceptually) add a parameter to input procedures, i.e. read, read-char, read-string, etc which is the object to return upon reaching the end of file (or end of stream).  The end of file can then be detected unambiguously by passing a unique object as this parameter and checking for eq? on the result of the input procedure.
>
> For `read' and friends, this is not a problem because an eof object is an object that cannot be read (by definition).

Unless you are using a Scheme system (such as Gambit, Chez, bigloo, etc) where the end-of-file object has an external representation, such as #!eof in Gambit and Chez.  The need to “serialize/deserialize” the end-of-file object happens in practice, for example if you want to do a remote procedure call and some parameter contains the end-of-file object or the result is the end-of-file object.

I see the need of serializing/deserializing an end-of-file object, but I would claim that the read/write protocol of R[567]RS is not the right protocol (because of the inherent assumption in it that the end-of-file object has no external representation).
 
Designing a language without considering these advanced use-cases will bite you down the line when you want to do these things and the only way is to add ugly special cases for the serialization/deserialization of the end-of-file object (instead of simply using write and read).

If I were to design the language from scratch, I wouldn't use a special guard object for reading and writing but some other mechanism.
 
> An optional parameter could be added to the input procedures but this would not be my choice.  Instead the R7RS eof-object procedure should be turned into a parameter object (intitially bound to a “standard” end of file object) and the input procedures would be required to return the value bound to the eof-object parameter object upon reaching the end of file).

One problem with this approach is that every library procedure that relies on the usual value of the end-of-file object (or some properties of it) will need to reparameterize just in case. 

This problem would not occur if an optional argument were added to the relevant procedures.
 
  I checked the R7RS and it seems this extension does not violate the current spec.
>
> Detecting the end of file unambiguously could then be done like this:
>
>   (let ((my-eof (list 'unique)))
>     (let ((data (parameterize ((eof-object my-eof)) (read port))))
>       (if (eq? data my-eof)
>           ...    ;; EOF reached
>           ...))) ;; EOF not yet reached
>
> While this works, it is obviously a hack in the sense that creating a unique object and testing against is not part of the abstract problem to be solved.
>
> An API that makes such hacks necessary is not a good API IMO.

This is not a “hack”… it is a well known pattern in Scheme/Lisp to handle special cases such as a dictionary lookup of a key that is not in the dictionary.  For this Gambit has the procedure (table-ref table key [default-value]) where default-value is optional and if it is specified and the key is not found the result is default-value.  By passing a unique object for this parameter it is possible to test if the dictionary contained the key with an eq? test of the unique object.  Racket has (dict-ref dict key [failure-result]) with similar spec and it is used to implement (dict-has-key? dict key).

In my opinion, it is still a hack, whether it is a well-known pattern or not. It is not a direct translation of the abstract algorithm but adds something (namely the creation and the test for a unique object). The R6RS hashtable interface has the same problem. SRFI 125 by Jown Cowan and Will Clinger solve it much better and drop the need of what I call a hack: (hash-table-ref HASH-TABLE KEY [FAILURE [SUCCESS]]). Admittedly, this is to some extent a matter of taste, but why repeat what other languages enforce because they have no proper tail calls?
 
In the case of read, an eof-object parameter object allows setting up a custom protocol to detect the “end of stream” between a stream generator (the read procedure) and the code that consumes the stream.

> PS: I am not sure whether your suggestion would agree with the R7RS spec. The R7RS spec says that `eof-object?' detects end-of-file objects and that `eof-object' returns end-of-file objects. This is not the case within the dynamic extent of the above parameterize.

In the R7RS the eof-object procedure is not a parameter object, so according to R7RS it is an error for a program to do (parameterize ((eof-object …)) …) or (eof-object 'my-eof).  Because it is an error the implementation (or SRFI spec) can do whatever it wants… i.e. all bets are off.  This is the traditional way in RnRS specifications to leave the door open for extensions such as the one I outlined and any extension proposed in a SRFI or even R8RS.

In this sense, you are completely right, of course.  A conformaning R7RS-small program would still be conforming.  (An R7RS-small library, however, may change its behavior.)  What I meant in my "I am not sure" remark was that if the possibility of parameterizing the eof object (procedure) is added as an option to the spec (thus removing that it would be an error), other parts of the spec (namely concerning `eof-object?') have to be changed as well.