Title

Keyword objects

Author

Marc Feeley

Status

This SRFI is currently in ``final'' status. To see an explanation of each status that a SRFI can hold, see here. To provide input on this SRFI, please mail to <srfi minus 88 at srfi dot schemers dot org>. See instructions here to subscribe to the list. You can access previous messages via the archive of the mailing list.

Abstract

This SRFI defines keyword objects, a data type similar to Scheme symbols. Keyword objects have the same lexical syntax as symbols but they must end in a colon. Moreover keyword objects are self-evaluating. Procedures for converting between strings and keyword objects (string->keyword and keyword->string) and a type predicate (keyword?) are defined. Finally this SRFI specifies the changes to the Scheme lexical syntax required to accomodate keywords.

Rationale

The motivation for keyword objects stems from the need for markers to identify optional named parameters, such as in SRFI 89 (Optional positional and named parameters). Here we mean ``parameter'' in the general sense of an API parameter (whether it is a syntactic or procedural API). Nevertheless we will use the terminology of procedures, namely the caller and callee, to identify the two sides of the API.

Optional named parameters are useful for APIs with many parameters, most of which have a reasonable default value. Using optional positional parameters in this case is not appropriate because it is hard to remember the position of a particular parameter P and it forces the caller to supply the values of parameters that come before P, whether it is the default value or not. Here are a few APIs where optional named parameters are particularly useful:

  1. Constructors for documents with attributes, such as HTML code:
        (html-p align: 'center
                (html-font face: "helvetica"
                           size: "+3"
                           "Title"))
    
  2. Hash-table constructor with many parameters (such as the minimum and maximum load factors, the initial size of the table, the key hash function and the key equality testing function to use, etc):
        (make-table size: 100000
                    test: string=?
                    hash: string-length)
    
  3. Widget constructors for GUIs:
        (tcltk-frame relief: 'ridge
                     borderwidth: 4
                     height: "50px"
                     width: "100px")
    
  4. A syntactic form for defining new record types and the generated record constructors:
        (define-type point
          id: _5c17ca1a-83e0-45eb-9c8e-74a03dfe89db
          x  ; x, y, z are fields required by the constructor
          y
          z)
    
        (define-type colored-point
          inherits: point
          id: _9d4ba977-2177-4650-8b96-2d781bfee771
          sealed: #t
          (color mutable: #f default: "black") ; color field is optional
          (layer default: 0 type: int8))       ; layer field is optional
    
        (define p1 (colored-point 1 2 3))
        (define p2 (colored-point 0 0 0 layer: 5))
    
  5. Port opening procedures that support several port options:
        (open-file (list path: "myfile"
                         direction: 'output
                         char-encoding: 'UTF-8
                         eol-encoding: 'cr-lf
                         append: #t))
    

The specification of the syntax for expressing optional named parameter (such as an extension to the parameter list syntax) is left for another SRFI, such as SRFI 89 (Optional positional and named parameters). This SRFI only specifies the keyword objects as a datatype.

Several implementations of Scheme currently feature keyword objects, often modeled after the keyword objects specified in the DSSSL standard or Common Lisp. Here is a partial list: Bigloo, Chicken, EdScheme, Gambit, Gauche, Guile, Jade (an implementation of DSSSL), Kawa, MzScheme, STKlos, and Sizzle.

Unfortunately few of these implementations are compatible. As far as lexical syntax is concerned, in some implementations the colon must come at the end (like in DSSSL), in some implementations it must come at the beginning (as in Common Lisp) possibly prefixed with a hash sign, and in other cases either at the beginning or end. This SRFI uses the trailing colon syntax because it does not conflict with existing Scheme code which use identifiers with leading colon, such as SRFI 42 (Eager Comprehensions). The main purpose of this SRFI is to help standardize keyword objects in these implementations and future implementations that support them.

Specification

Like symbols, keywords are objects whose usefulness rests on the fact that two keywords are identical (in the sense of eqv?) if and only if their names are spelled the same way. However, keywords and symbols are distinct datatypes. No keyword is identical to a symbol (in the sense of eqv?) or any object of another datatype.

At the lexical syntax level, keywords look like symbols that end with a colon. The R5RS lexical syntax category for symbols is <identifier>. This category must be changed so that an identifier cannot end with a colon unless the symbol is : (a symbol consisting of only a colon). The categories <token> and <simple datum> need to be extended and the category <keyword object> needs to be added as follows:

<token> --> <identifier> | <boolean> | <number>
     | <character> | <string>
     | ( | ) | #( | ' | ` | , | ,@ | . | <keyword object>

<simple datum> --> <boolean> | <number>
     | <character> | <string> |  <symbol> | <keyword object>

<keyword object> --> <identifier> :

Note that the rule for <keyword object> must be valid even in Scheme implementations that have extended the <identifier> syntax to support quoted symbols as specified in the original specification of SRFI 75 (R6RS Unicode data). In other words, in such implementations, |foo|: and foo: are the same keyword, and |foo:| is a symbol, not a keyword, and ||: is a keyword. Using this quoted keyword lexical syntax all keyword objects produced by the string->keyword procedure and written with the write procedure will be read back in with the read procedure as the identical keyword (in the sense of eqv?).

Keywords are self evaluating. The syntax of expressions must be extended by modifying the category <self-evaluating> to include <keyword object>:

<self-evaluating> --> <boolean> | <number>
     | <character> | <string> | <keyword object>
procedure: (keyword? obj)

Returns #t if obj is a keyword object, otherwise returns #f.

(keyword? 'foo:)                    ==>  #t
(keyword? foo:)                     ==>  #t
(keyword? 'foo)                     ==>  #f
(keyword? ':)                       ==>  #f
(keyword? ||:)                      ==>  #t  (if keyword quoting supported)
(keyword? (car '(a: b:)))           ==>  #t
(keyword? "bar")                    ==>  #f
procedure: (keyword->string keyword)

Returns the name of keyword as a string. It is an error to apply mutation procedures like string-set! to strings returned by this procedure.

(keyword->string foo:)              ==>  "foo"
(keyword->string ||:)               ==>  ""  (if keyword quoting supported)
(keyword->string
   (string->keyword "a b c"))       ==>  "a b c"
procedure: (string->keyword string)

Returns the keyword object whose name is string.

(string->keyword "foo")             ==>  foo:
(string->keyword "")                ==>  ||:  (if keyword quoting supported)

Implementation

The implementation of keyword objects requires changing the reader and the evaluator. Although the changes are expected to be straightforward they are obviously highly system dependent. Here we offer a simplistic `user-level' implementation (a.k.a. kludge) that will work in Scheme implementations that allow colons anywhere in the symbol. The following implementation redefines the symbol related primitives so that symbols ending with a colon are considered to be keywords, not symbols.

    (define real-symbol? symbol?)
    (define real-symbol->string symbol->string)
    (define real-string->symbol string->symbol)

    (define looks-like-an-unquoted-keyword?
      (lambda (s)
        (let ((n (string-length s)))
          (and (> n 1)
               (char=? (string-ref s (- n 1)) #\:)))))

    (set! symbol?
      (lambda (obj)
        (and (real-symbol? obj)
             (not (looks-like-an-unquoted-keyword?
                   (real-symbol->string obj))))))

    (define keyword?
      (lambda (obj)
        (and (real-symbol? obj)
             (looks-like-an-unquoted-keyword?
              (real-symbol->string obj)))))

    (set! symbol->string real-symbol->string)

    (define keyword->string
      (lambda (k)
        (let* ((s (real-symbol->string k))
               (n (string-length s)))
          (substring s 0 (- n 1))))) ; remove the colon

    (set! string->symbol
      (lambda (s)
        (if (looks-like-an-unquoted-keyword? s)
            (error "sorry... the symbol would look like a keyword!")
            (real-string->symbol s))))

    (define string->keyword
      (lambda (s)
        (let ((s-colon (string-append s ":")))
          (if (looks-like-an-unquoted-keyword? s-colon)
              (real-string->symbol s-colon)
              (error "sorry... the keyword would look like a symbol!")))))

Note that this code does not implement the self-evaluation property of keyword objects. If the set of keyword objects used by a program is known in advance, self-evaluation can be simulated with a set of top-level definitions such as:

    (define foo: 'foo:)
    (define bar: 'bar:)

Although the implementation presented here is not generally applicable, it may be useful to bootstrap a complete implementation of keyword objects in a compiler.

Copyright

Copyright (C) Marc Feeley (2006). 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.


Editor: Donovan Kolbly