Code sample pair: SRFI-9 David A. Wheeler 16 Mar 2013 16:00 UTC

Here's a code sample from SRFI-9, in both s-expressions (the original) and sweet-expressions.  This demonstrates how these s-expressions can be represented as sweet-expressions.  My hope is that this (and other examples) shows that sweet-expressions are readable to a wider audience.

I'm pasting it below; you can get these as files from the http://readable.sourceforge.net git repo ("develop" branch).  In the sweet-expressions I'm always using "!" as the first indent character, which is certainly not required but it deals with mail readers that gobble up leading spaces.

 --- David A. Wheeler

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; As s-expressions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This code is from SRFI-9 by Richard Kelsey,
; http://srfi.schemers.org/srfi-9/srfi-9.html
; According to "Scheme Systems Supporting SRFIs" by John Cowan
; (http://spreadsheets.google.com/pub?key=tRCHK6jWXuKMABKAfoOwWqw&output=html)
; this is the most widely implemented Scheme SFRI.

; Syntax definitions

; Definition of DEFINE-RECORD-TYPE

(define-syntax define-record-type
  (syntax-rules ()
    ((define-record-type type
       (constructor constructor-tag ...)
       predicate
       (field-tag accessor . more) ...)
     (begin
       (define type
         (make-record-type 'type '(field-tag ...)))
       (define constructor
         (record-constructor type '(constructor-tag ...)))
       (define predicate
         (record-predicate type))
       (define-record-field type field-tag accessor . more)
       ...))))

; An auxilliary macro for define field accessors and modifiers.
; This is needed only because modifiers are optional.

(define-syntax define-record-field
  (syntax-rules ()
    ((define-record-field type field-tag accessor)
     (define accessor (record-accessor type 'field-tag)))
    ((define-record-field type field-tag accessor modifier)
     (begin
       (define accessor (record-accessor type 'field-tag))
       (define modifier (record-modifier type 'field-tag))))))

; Record types

; We define the following procedures:
;
; (make-record-type <type-name <field-names>)    -> <record-type>
; (record-constructor <record-type<field-names>) -> <constructor>
; (record-predicate <record-type>)               -> <predicate>
; (record-accessor <record-type <field-name>)    -> <accessor>
; (record-modifier <record-type <field-name>)    -> <modifier>
;   where
; (<constructor> <initial-value> ...)         -> <record>
; (<predicate> <value>)                       -> <boolean>
; (<accessor> <record>)                       -> <value>
; (<modifier> <record> <value>)         -> <unspecific>

; Record types are implemented using vector-like records.  The first
; slot of each record contains the record's type, which is itself a
; record.

(define (record-type record)
  (record-ref record 0))

;----------------
; Record types are themselves records, so we first define the type for
; them.  Except for problems with circularities, this could be defined as:
;  (define-record-type :record-type
;    (make-record-type name field-tags)
;    record-type?
;    (name record-type-name)
;    (field-tags record-type-field-tags))
; As it is, we need to define everything by hand.

(define :record-type (make-record 3))
(record-set! :record-type 0 :record-type)	; Its type is itself.
(record-set! :record-type 1 ':record-type)
(record-set! :record-type 2 '(name field-tags))

; Now that :record-type exists we can define a procedure for making more
; record types.

(define (make-record-type name field-tags)
  (let ((new (make-record 3)))
    (record-set! new 0 :record-type)
    (record-set! new 1 name)
    (record-set! new 2 field-tags)
    new))

; Accessors for record types.

(define (record-type-name record-type)
  (record-ref record-type 1))

(define (record-type-field-tags record-type)
  (record-ref record-type 2))

;----------------
; A utility for getting the offset of a field within a record.

(define (field-index type tag)
  (let loop ((i 1) (tags (record-type-field-tags type)))
    (cond ((null? tags)
           (error "record type has no such field" type tag))
          ((eq? tag (car tags))
           i)
          (else
           (loop (+ i 1) (cdr tags))))))

;----------------
; Now we are ready to define RECORD-CONSTRUCTOR and the rest of the
; procedures used by the macro expansion of DEFINE-RECORD-TYPE.

(define (record-constructor type tags)
  (let ((size (length (record-type-field-tags type)))
        (arg-count (length tags))
        (indexes (map (lambda (tag)
                        (field-index type tag))
                      tags)))
    (lambda args
      (if (= (length args)
             arg-count)
          (let ((new (make-record (+ size 1))))
            (record-set! new 0 type)
            (for-each (lambda (arg i)
			(record-set! new i arg))
                      args
                      indexes)
            new)
          (error "wrong number of arguments to constructor" type args)))))

(define (record-predicate type)
  (lambda (thing)
    (and (record? thing)
         (eq? (record-type thing)
              type))))

(define (record-accessor type tag)
  (let ((index (field-index type tag)))
    (lambda (thing)
      (if (and (record? thing)
               (eq? (record-type thing)
                    type))
          (record-ref thing index)
          (error "accessor applied to bad value" type tag thing)))))

(define (record-modifier type tag)
  (let ((index (field-index type tag)))
    (lambda (thing value)
      (if (and (record? thing)
               (eq? (record-type thing)
                    type))
          (record-set! thing index value)
          (error "modifier applied to bad value" type tag thing)))))

; Records

; This implements a record abstraction that is identical to vectors,
; except that they are not vectors (VECTOR? returns false when given a
; record and RECORD? returns false when given a vector).  The following
; procedures are provided:
;   (record? <value>)                -> <boolean>
;   (make-record <size>)             -> <record>
;   (record-ref <record> <index>)    -> <value>
;   (record-set! <record> <index> <value>) -> <unspecific>
;
; These can implemented in R5RS Scheme as vectors with a distinguishing
; value at index zero, providing VECTOR? is redefined to be a procedure
; that returns false if its argument contains the distinguishing record
; value.  EVAL is also redefined to use the new value of VECTOR?.

; Define the marker and redefine VECTOR? and EVAL.

(define record-marker (list 'record-marker))

(define real-vector? vector?)

(define (vector? x)
  (and (real-vector? x)
       (or (= 0 (vector-length x))
	   (not (eq? (vector-ref x 0)
		record-marker)))))

; This won't work if ENV is the interaction environment and someone has
; redefined LAMBDA there.

(define eval
  (let ((real-eval eval))
    (lambda (exp env)
      ((real-eval `(lambda (vector?) ,exp))
       vector?))))

; Definitions of the record procedures.

(define (record? x)
  (and (real-vector? x)
       (< 0 (vector-length x))
       (eq? (vector-ref x 0)
            record-marker)))

(define (make-record size)
  (let ((new (make-vector (+ size 1))))
    (vector-set! new 0 record-marker)
    new))

(define (record-ref record index)
  (vector-ref record (+ index 1)))

(define (record-set! record index value)
  (vector-set! record (+ index 1) value))

; Copyright (C) Richard Kelsey (1999). All Rights Reserved.
;
; Permission is hereby granted, free of charge, to any person obtaining a
; copy of this software and associated documentation files (the "Software"),
; to deal in the Software without restriction, including without limitation
; the rights to use, copy, modify, merge, publish, distribute, sublicense,
; and/or sell copies of the Software, and to permit persons to whom the
; Software is furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included
; in all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
; OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
; OTHER DEALINGS IN THE SOFTWARE.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; As sweet-expressions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This code is from SRFI-9 by Richard Kelsey,
; http://srfi.schemers.org/srfi-9/srfi-9.html
; According to "Scheme Systems Supporting SRFIs" by John Cowan
; (http://spreadsheets.google.com/pub?key=tRCHK6jWXuKMABKAfoOwWqw&output=html)
; this is the most widely implemented Scheme SFRI.

; Syntax definitions

; Definition of DEFINE-RECORD-TYPE

define-syntax define-record-type
! syntax-rules ()
!   \\
!     define-record-type
!       type
!       constructor constructor-tag ...
!       predicate
!       field-tag accessor . more
!       ...
!     begin
!       define type
!       ! make-record-type 'type 'field-tag(...)
!       define constructor
!       ! record-constructor type 'constructor-tag(...)
!       define predicate record-predicate(type)
!       define-record-field type field-tag accessor . more
!       ...

; An auxilliary macro for define field accessors and modifiers.
; This is needed only because modifiers are optional.

define-syntax define-record-field
! syntax-rules ()
!   define-record-field(type field-tag accessor)
!     define accessor record-accessor(type 'field-tag)
!   define-record-field(type field-tag accessor modifier)
!     begin
!       define accessor record-accessor(type 'field-tag)
!       define modifier record-modifier(type 'field-tag)

; Record types

; We define the following procedures:
;
; (make-record-type <type-name <field-names>)    -> <record-type>
; (record-constructor <record-type<field-names>) -> <constructor>
; (record-predicate <record-type>)               -> <predicate>
; (record-accessor <record-type <field-name>)    -> <accessor>
; (record-modifier <record-type <field-name>)    -> <modifier>
;   where
; (<constructor> <initial-value> ...)         -> <record>
; (<predicate> <value>)                       -> <boolean>
; (<accessor> <record>)                       -> <value>
; (<modifier> <record> <value>)         -> <unspecific>

; Record types are implemented using vector-like records.  The first
; slot of each record contains the record's type, which is itself a
; record.

define record-type(record) record-ref(record 0)

;----------------
; Record types are themselves records, so we first define the type for
; them.  Except for problems with circularities, this could be defined as:
;  (define-record-type :record-type
;    (make-record-type name field-tags)
;    record-type?
;    (name record-type-name)
;    (field-tags record-type-field-tags))
; As it is, we need to define everything by hand.

define :record-type make-record(3)
record-set! :record-type 0 :record-type; Its type is itself.
record-set! :record-type 1 ':record-type
record-set! :record-type 2 'name(field-tags)

; Now that :record-type exists we can define a procedure for making more
; record types.

define make-record-type(name field-tags)
! let <* new $ make-record 3 *>
!   record-set! new 0 :record-type
!   record-set! new 1 name
!   record-set! new 2 field-tags
!   new

; Accessors for record types.

define record-type-name(record-type)
! record-ref record-type 1

define record-type-field-tags(record-type)
! record-ref record-type 2

;----------------
; A utility for getting the offset of a field within a record.

define field-index(type tag)
! let loop <* i 1 \\ tags $ record-type-field-tags type *>
!   cond
!     null?(tags) error("record type has no such field" type tag)
!     eq?(tag car(tags)) i
!     else loop({i + 1} cdr(tags))

;----------------
; Now we are ready to define RECORD-CONSTRUCTOR and the rest of the
; procedures used by the macro expansion of DEFINE-RECORD-TYPE.

define record-constructor(type tags)
! let
!   \\
!     size      $ length record-type-field-tags(type)
!     arg-count $ length tags
!     indexes   $ map lambda(tag() field-index(type tag)) tags
!   lambda args
!     if {length(args) = arg-count}
!       let (new(make-record{size + 1}))
!       ! record-set! new 0 type
!       ! for-each lambda(arg(i) record-set!(new i arg)) args indexes
!       ! new
!       error "wrong number of arguments to constructor" type args

define record-predicate(type)
! lambda thing()
!   {record?(thing) and eq?(record-type(thing) type)}

define record-accessor(type tag)
! let <* index $ field-index type tag *>
!   lambda thing()
!     if {record?(thing) and eq?(record-type(thing) type)}
!       record-ref thing index
!       error "accessor applied to bad value" type tag thing

define record-modifier(type tag)
! let <* index $ field-index type tag *>
!   lambda thing(value)
!     if {record?(thing) and eq?(record-type(thing) type)}
!       record-set! thing index value
!       error "modifier applied to bad value" type tag thing

; Records

; This implements a record abstraction that is identical to vectors,
; except that they are not vectors (VECTOR? returns false when given a
; record and RECORD? returns false when given a vector).  The following
; procedures are provided:
;   (record? <value>)                -> <boolean>
;   (make-record <size>)             -> <record>
;   (record-ref <record> <index>)    -> <value>
;   (record-set! <record> <index> <value>) -> <unspecific>
;
; These can implemented in R5RS Scheme as vectors with a distinguishing
; value at index zero, providing VECTOR? is redefined to be a procedure
; that returns false if its argument contains the distinguishing record
; value.  EVAL is also redefined to use the new value of VECTOR?.

; Define the marker and redefine VECTOR? and EVAL.

define record-marker list('record-marker)

define real-vector? vector?

define vector?(x)
! and
!   real-vector? x
!   or
!     {0 = vector-length(x)}
!     not eq?(vector-ref(x 0) record-marker)

; This won't work if ENV is the interaction environment and someone has
; redefined LAMBDA there.

define eval
! let <* real-eval eval *>
!   lambda exp(env)
!     real-eval(`lambda(vector?() ,exp)) vector?

; Definitions of the record procedures.

define record?(x)
! and
!   real-vector? x
!   {0 < vector-length(x)}
!   eq? vector-ref(x 0) record-marker

define make-record(size)
! let <* new make-vector{size + 1} *>
!   vector-set! new 0 record-marker
!   new

define record-ref(record index)
! vector-ref record {index + 1}

define record-set!(record index value)
! vector-set! record {index + 1} value

; Copyright (C) Richard Kelsey (1999). All Rights Reserved.
;
; Permission is hereby granted, free of charge, to any person obtaining a
; copy of this software and associated documentation files (the "Software"),
; to deal in the Software without restriction, including without limitation
; the rights to use, copy, modify, merge, publish, distribute, sublicense,
; and/or sell copies of the Software, and to permit persons to whom the
; Software is furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included
; in all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
; OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
; OTHER DEALINGS IN THE SOFTWARE.