port-utilities


Why use Port Utilities?

Reading and writing from/to ports (i.e. files, pipes sockets etc.) are common tasks that can often be rather verbose and awkward when expressed in raw R5RS scheme. They can be made less bothersome by using utilities, i.e. procedures and macros that factor out routine actions. These are often small, trivial definitions that nevertheless make a scripter's life easier.

Reading lines

The read-line procedure is not defined in any standard, but is almost universally provided by Scheme implementations. When using a Scheme that for some reason doesn't have it, it's easy to define our own (using string ports from SRFI 6):

 (define (read-line . maybe-inport) 
   (let ((inport  (if (pair? maybe-inport) 
                      (car maybe-inport) 
                      (current-input-port))) 
         (outport (open-output-string))) 
     (let loop () 
       (let ((char (read-char inport))) 
         (cond ((eof-object? char) 
                char) 
               ((eqv? char #\newline) 
                (get-output-string outport)) 
               (else 
                (write-char char outport) 
                (loop))))))) 

pitecus

This definition could be improved by taking into account the differing newline conventions (LF vs LF+CR vs CR) across operating systems.


soegaard

See also two alternative solutions at Cookbook FileReadingLines.


Printing lines

Often one wants to print one or more strings to a port and append a newline character at the end. This is awkward with display & newline but the following convenience procedure is used in some implementations:

 (define (print . args) 
   (for-each display args) 
   (newline)) 

This procedure can also replace some uses of FORMAT from SRFI-48? and is a nice alternative to string interpolation know from Perl or Ruby. E.g.

 (print "The temperature is "  
        temperature  
        " °C and the pressure is " 
        pressure  
        " hPa.") 

Port iterators

Often one needs to read sequentially some chunk such as line, paragraph, sexpression or character from port and perfom some operation on each of them. To ease that, we can use port iterators. The reader argument they take is a procedure which returns either the EOF object (at which point the iteration stops) or some other Scheme object (e.g. string) which is passed to the fn procedure.

port-fold

 (define (port-fold fn knil reader) 
   (let loop ((knil knil)) 
     (let ((item (reader))) 
       (if (eof-object? item) 
           knil 
           (loop (fn item knil)))))) 

port-for-each

 (define (port-for-each fn reader) 
   (let loop () 
     (let ((item (reader))) 
       (cond ((not (eof-object? item)) 
              (fn item) 
              (loop)))))) 

port-map

 (define (port-map fn reader) 
   (port-fold cons '() reader)) 

Example usage

Print a list of palindromes found in the Unix word list (using, in addition to port utilities, string-reverse from SRFI 13):

 (define (print-palindromes) 
   (with-input-from-file "/usr/share/dict/words" 
     (lambda () 
       (port-for-each (lambda (word) 
                        (if (string-ci=? word (string-reverse word)) 
                            (display word))) 
                      read-line)))) 

category-code