On 14-Apr-06, at 10:53 AM, Taylor R. Campbell wrote: > Date: Fri, 14 Apr 2006 09:37:23 -0400 > From: Marc Feeley <xxxxxx@iro.umontreal.ca> > > On 14-Apr-06, at 8:49 AM, John Cowan wrote: > >> Marc Feeley scripsit: >> >>> It is a pain to carry those two ports around in the code when the >>> program needs to communicate bidirectionally with some other entity >>> (another process, a user at a terminal, a socket, etc). Moreover >>> the >>> separation of a conceptually bidirectional channel into distinct >>> ports (input and output) destroys the conceptual link that they >>> have. This hinders program understanding. For example, with >>> bidirectional ports (close-port port) will close both sides of the >>> bidirectional port (i.e. the link between the input and output port >>> is preserved). With two unidirectional ports you have to duplicate >>> some operations (closing ports, changing port settings, ...). >> >> I find this rationale convincing (and think it should be added to the >> SRFI). > > OK. > > I find this rational unconvincing: I see no reason why introducing a > third object (say socket, tty, &c.) would hinder program understanding > any more than the bidirectional ports do -- personally, I think > bidirectional ports do more to hinder it. I view these other types of ports as subtypes of byte ports, or character ports, or object ports. See below. > > If we introduce this third object, then we can simplify the model of > input and output ports very much, so that an input port is purely a > source and an output port purely a sink, and we can specify any > options common to the resource that they are both associated with on > the third object; for instance, we could have a (SET-SOCKET-OPTION > socket level option value). This furthermore simplifies issues about > closing; see below. > > Continuing on the socket theme, port I/O doesn't even make sense for > some sockets, such as stream listener sockets and unconnected datagram > sockets. In these cases it would be absurd to represent the socket as > a port, while it makes very much sense to represent it as a distinct > `socket' object, from which in certain cases (specifically, connected > sockets) one can obtain ports. > This may surprise you but that is exactly how stream listener sockets are handled in Gambit, where tcp-server ports (stream listener sockets) are object ports (the generalization of character ports which is mentioned in the SRFI). When the program reads from a tcp- server port (with the read procedure) the program blocks until a connection is established from a client. The read procedure returns a bidirectional byte port (tcp-client port) which represents the connection. In other words: (define (start-http-server) (let ((http-listener (open-tcp-server 80))) (let loop () (handle-connection (read)) (loop)))) (define (handle-connection port) (let ((request (read-line port))) ... (close-port port))) As you can see this gives a nice high-level interface to stream listener sockets. Gambit also provides an object port interface to directories: (define (for-each-file-in-directory path proc) (let ((dir (open-directory path))) (let loop () (let ((file (read dir))) (if (not (eof-object? file)) (begin (proc file) (loop))))))) (for-each-file-in-directory "/" pretty-print) Doing it this way has the advantage of avoiding using lots of memory to hold the list of file names when the directory is very long. Of course, for convenience Gambit also provides a (directory-files path) procedure that returns a list of file names. Object ports are also useful for inter-thread communication, as FIFOs. This is convenient for a message passing paradigm where objects are exchanged between threads (and serializing them with characters or bytes would be a waste of time or would alter the semantics of the objects (eq?-ness, cycles, etc)). To be clear: I want to keep the high-level nature of the R5RS I/O subsystem. I think a lot of useful things can be done within that high-level framework as shown by this SRFI. I *strongly* dislike some I/O systems where you need to create layer upon layer to achieve what is fundamentally a simple I/O task. As an example taken from a Java tutorial on the web: File outFile; PrintWriter pw; outFile = new File("output.text"); if (! outFile.exists() || (outFile.isFile() && outFile.canWrite())) { pw = new PrintWriter(new BufferedWriter(new FileWriter (outFile))); ... } This is plain ugly. This is one of the main reasons I dislike SRFI 68 (Comprehensive I/O). > I think this is actually a bad idea. It's not entirely clear when the > resource itself gets closed, versus when individual directions are > closed. CLOSE-PORT, it seems, cannot be defined as > > (define (close-port port) > (if (input-port? port) (close-input-port port)) > (if (output-port? port) (close-output-port port))), An implementation of the SRFI can be smart and optimize the implementation of the close-port procedure so that a single low-level close is performed on the low-level device. But in principle the implementation you give above is correct (at least for the types of ports we are considering in this SRFI). The implementation can easily keep a one bit reference count to tell when both sides have been closed. Do you truly view this as an implementation problem? Marc