guile-srfi-123

guile-srfi-123 is an SRFI-123 implementation for Guile. (More on SRFI-123 on https://srfi.schemers.org/srfi-123/)
git clone https://kaka.farm/~git/guile-srfi-123
Log | Files | Refs

srfi-123.md (14328B)


      1 % Generic accessor and modifier operators
      2 
      3 Author
      4 ------
      5 
      6 Taylan Ulrich Bayırlı/Kammer, taylanbayirli at Google Mail
      7 
      8 
      9 Status
     10 ------
     11 
     12 This SRFI is currently in *final* status.  Here is
     13 [an explanation](http://srfi.schemers.org/srfi-process.html) of each
     14 status that a SRFI can hold.
     15 
     16 To provide input on this SRFI, please
     17 send email to <code><a href="mailto:srfi minus 123 at srfi dot
     18 schemers dot org">srfi-123@<span
     19 class="antispam">nospam</span>srfi.schemers.org</a></code>.  To
     20 subscribe to the list, follow
     21 [these instructions](http://srfi.schemers.org/srfi-list-subscribe.html).
     22 You can access previous messages via the mailing list
     23 [archive](http://srfi-email.schemers.org/srfi-123).
     24 
     25   - Received: 2015/8/14
     26   - Draft #1 published: 2015/8/15
     27   - Draft #2 published: 2015/8/16
     28   - Draft #3 published: 2015/8/17
     29   - Draft #4 published: 2015/8/18
     30   - Draft #5 published: 2015/8/23 (code changes only)
     31   - Draft #6 published: 2015/8/24
     32   - Draft #7 published: 2015/8/26
     33   - Draft #8 published: 2015/9/5
     34   - Draft #9 published: 2015/9/7
     35   - Finalized: 2015/10/4
     36 
     37 
     38 Abstract
     39 --------
     40 
     41 Lisp dialects including Scheme have traditionally lacked short,
     42 simple, generic syntax for accessing and modifying the fields of
     43 arbitrary "collection" objects.  We fill this gap for Scheme by
     44 defining generalized accessors, and an associated SRFI-17 setter.
     45 
     46 
     47 Rationale
     48 ---------
     49 
     50 In some types of code-bases, accessing and modifying fields of certain
     51 collection objects (such as vectors, hashtables, or records) are
     52 ubiquitous operations.  Standard Scheme APIs contain verbose procedure
     53 names specialized for each data type, which may become very tedious to
     54 type, and clutter the code.
     55 
     56 In contrast, most other languages offer very short and simple syntax
     57 for such operations, such as square bracket and dotted notation:
     58 `object[field]` and `object.field` for access; `object[field] = value`
     59 and `object.field = value` for modification.
     60 
     61 To accommodate, we define a pair of generic accessor operators that
     62 work through type-based dynamic dispatch: `(ref object field)`, and
     63 `(ref* object field1 field2 ...)` for chained access.
     64 
     65     (ref #(a b c) 1)  ;=> b
     66     (ref* #(a (x y #u8(1 2 3)) c) 1 2 0)  ;=> 1
     67 
     68 We define `~` as a synonym to `ref*`, and define a SRFI-17 setter for
     69 it.
     70 
     71     (define struct #(a (x y #u8(1 2 3)) c))
     72     (set! (~ struct 1 2 0) 4)
     73     struct  ;=> #(a (x y #u8(4 2 3)) c)
     74 
     75 Plain `ref`, instead of allowing chaining, takes an optional "default"
     76 argument for objects such as hashtables.
     77 
     78     (define table (make-eqv-hashtable))
     79     (ref table "foo" 'not-found)  ;=> not-found
     80     (set! (~ table "foo") "Foobar.")
     81     (ref table "foo" 'not-found)  ;=> "Foobar."
     82 
     83 Lack of a default argument is an error in this case.  Since `ref*`
     84 cannot take default arguments for any fields it accesses, it is an
     85 error when a hashtable key in the chain is not found.
     86 
     87     (define table (make-eqv-hashtable))
     88     (define lst (list 0 1 table 3))
     89     (ref* lst 2 "foo" 'x)  ;error while accessing "foo" from table
     90 
     91 We believe the overhead involved in the dynamic dispatch is negligible
     92 in most cases, and furthermore a programmer can always fall back to
     93 type-specific accessor and modifier procedures in performance-critical
     94 sections of code.
     95 
     96 The operators are specified to work on bytevectors, R6RS hashtables,
     97 lists/pairs, strings, vectors, non-opaque record types, SRFI-4
     98 vectors, and SRFI-111 boxes.  (R6RS and SRFI-99 can produce opaque
     99 record types; SRFI-9 and R7RS cannot.)  Some notes on specific types:
    100 
    101 - For bytevectors, 8-bit unsigned integer operations are assumed.
    102   There is no obvious way to incorporate other bytevector operations
    103   into the generalized API, and a programmer is most likely to have
    104   single-byte operations in mind when using a generalized API on
    105   bytevectors.
    106 
    107     ```
    108     (define bv (bytevector 0 1 2 3))
    109     (ref bv 2)  ;=> 2
    110     (set! (~ bv 2) 5)
    111     (ref bv 2)  ;=> 5
    112     ```
    113 
    114 - However, some implementations provide SRFI-4 vectors by tagging
    115   bytevectors, such that SRFI-4 vectors are not disjoint types from
    116   bytevectors.  In that case, the SRFI-4 type of the bytevector
    117   dictates the semantics.
    118 
    119     ```
    120     (define bv (s16vector 0 1 2 3))
    121     (bytevector? bv)  ;=> #t
    122     (bytevector-u8-ref bv 2)  ;=> (result depends on endianness)
    123     (ref bv 2)  ;=> 2
    124     ```
    125 
    126 - When a pair is encountered, the field argument may be the symbols
    127   `car` or `cdr`, or an integer index indicating the pair should be
    128   viewed as the head of a list.
    129 
    130     ````
    131     (ref '(a b c . d) 'cdr)  ;=> (b c . d)
    132     (ref '(a b c . d) 2)  ;=> c
    133     ````
    134 
    135 - For records, the accepted values for the `field` parameter are
    136   symbols corresponding to the record type's field names.  The
    137   overhead involved in looking up the correct accessor of modifier
    138   falls under the same rationale as other kinds of overhead involved
    139   with this SRFI.
    140 
    141     ```
    142     (define-record-type <foo> (make-foo a b) foo?
    143       (a foo-a set-foo-a!)
    144       (b foo-b))
    145     (define foo (make-foo 0 1))
    146     (ref foo 'a)  ;=> 0
    147     (set! (~ foo 'b) 2)  ;error: No such assignable field of record.
    148     ```
    149 
    150 - For boxes, the symbol `*` is used to indicate the one value field of
    151   the box.  This is mainly useful for `ref*`:
    152 
    153     ```
    154     (define struct (list 0 (vector (box (cons 'a 'b)))))
    155     (ref* struct 1 0 '* 'cdr)
    156     ```
    157 
    158 Alists are difficult to support due to the lack of a reliable `alist?`
    159 predicate.  (It's ambiguous in that every alist is also a list, and
    160 any list may coincidentally have the structure of an alist.)  It was
    161 considered to support non-integer keyed alists as a special case, but
    162 this would lead to silent code breakage when a programmer forgot about
    163 the API inconsistency and exchanged a non-integer key for an integer
    164 key in existing code.  It was also considered to drop list support in
    165 favor of alist support, but that idea discarded as well because the
    166 hypothetical `alist-set!` is an exceedingly rare operation.
    167 (Prepending an entry to the front, possibly hiding another entry with
    168 the same key, is more common.)
    169 
    170 
    171 Integration with SRFI-105
    172 -------------------------
    173 
    174 The `ref*` procedure is a good candidate for SRFI-105's
    175 `$bracket-apply$`.  Indeed the sample implementation exports
    176 `$bracket-apply$` as a synonym to `ref*`.  In code that already uses
    177 SRFI-105 heavily, a programmer may additionally define `:=` as a
    178 synonym to `set!`, and then use the following syntax:
    179 `{object[field] := value}`.
    180 
    181     #!curly-infix
    182     (import (rename (only (scheme base) set!) (set! :=)))
    183     (define vec (vector 0 1 2 3))
    184     {vec[1] + vec[2]}  ;=> 3
    185     {vec[2] := 4}
    186     {vec[1] + vec[2]}  ;=> 5
    187 
    188 The square brackets accept a chain of fields, since they have the
    189 semantics of `ref*`: `{matrix[i j]}`.
    190 
    191 
    192 Specification
    193 -------------
    194 
    195 Within this section, whenever a situation is described as being an
    196 error, a Scheme implementation supporting error signaling should
    197 signal an error.
    198 
    199 - `(ref object field)` (procedure)
    200 - `(ref object field default)`
    201 
    202 Returns the value for `field` in `object`.  It is an error if `object`
    203 has no field identified by `field`.
    204 
    205     (ref #(0 1 2) 3)  ;error: vector-ref: Index out of bounds.
    206 
    207 If `object` is of a "sparse" type, meaning its fields can be "empty"
    208 or "unassigned" (e.g. a hashtable), and the requested field is empty,
    209 then the value of `default` is returned.  It is an error if the
    210 `default` argument is not provided in this case.
    211 
    212     (ref hashtable unassigned-key 'default)  ;=> default
    213     (ref hashtable unassigned-key)  ;error
    214 
    215 If `object` is not of a sparse type, then providing the `default`
    216 argument is an error.
    217 
    218     (ref '(0 1 2) 3 'default)  ;error: list-ref: Too many arguments.
    219 
    220 Valid types for `object` are: bytevectors, hashtables, pairs, strings,
    221 vectors, non-opaque record types, SRFI-4 vectors, and SRFI-111 boxes.
    222 Only hashtables are a sparse type.  Implementations are encouraged to
    223 expand this list of types with any further types they support.
    224 
    225 Valid types for `field` depend on the type of `object`.  For
    226 bytevectors, hashtables, strings, vectors, and SRFI-4 vectors, refer
    227 to their respective `*-ref` procedures.  For pairs, the symbols `car`
    228 and `cdr` are accepted, as well as non-negative integers as with
    229 `list-ref`.  For records, symbols that correspond with the record
    230 type's field names are allowed.  For boxes, the symbol `*` is used to
    231 denote the one value field of the box.
    232 
    233 A conforming implementation must be prepared for SRFI-4 vector types
    234 and bytevectors not being disjoint types, and treat SRFI-4 vectors
    235 suitably and not as regular bytevectors.
    236 
    237 A conforming implementation must also be prepared for boxes being a
    238 non-opaque record type instead of a disjoint type, and treat them
    239 correctly despite that fact.
    240 
    241 The `ref` procedure has an associated SRFI-17 setter, although the one
    242 of `ref*` is strictly more powerful.
    243 
    244     (define vec (vector 0 1 2))
    245     (set! (ref vec 0) 3)
    246     vec  ;=> #(3 1 2)
    247 
    248 - `(ref* object field field* ...)` (procedure)
    249 - `(~ object field field* ...)`
    250 
    251 The semantics of this procedure is as follows:
    252 
    253     (ref* object field)            = (ref object field)
    254     (ref* object field field+ ...) = (ref* (ref object field) field+ ...)
    255 
    256 It has an associated SRFI-17 setter, which does the expected thing:
    257 
    258     (set! (~ obj f1 f2 f3) value)
    259 
    260 changes the value that would be returned from `(~ obj f1 f2 f3)` to
    261 `value`.  Note that this procedure can be accessed as `(setter ref*)`
    262 when needed:
    263 
    264     (define (store-item! field-chain value)
    265       (apply (setter ref*) the-store (append field-chain (list value))))
    266 
    267 - `(register-getter-with-setter! type getter sparse?)` (procedure)
    268 
    269 Registers a new type/getter/setter triple for the dynamic dispatch.
    270 `Type` is a type predicate, `getter` is a procedure that has a setter
    271 associated with it, and `sparse?` is a Boolean indicating whether the
    272 type is a sparse type (see `ref` specification).
    273 
    274 The getter will be called with two arguments: the object whose field
    275 should be accessed, and an object identifying the field to be
    276 accessed.  The setter will be called with one additional argument
    277 which is the value to be assigned to the given field of the given
    278 object.
    279 
    280 **Warning:** This procedure is strictly meant for when defining a new
    281 disjoint type which isn't already handled by `ref`.  In practice, this
    282 means it should only be used with newly defined opaque record types,
    283 or types defined with some implementation-specific method which,
    284 unlike `define-record-type`, doesn't automatically register a getter
    285 and setter for the type.  If any two type predicates registered with
    286 the system both return true for any Scheme object, the behavior is
    287 undefined.  (A custom getter or setter may, however, dispatch to
    288 different actions based on some property of the given object, based on
    289 the `field` argument, or based on anything else.)
    290 
    291 It is conceivable that this method will become deprecated after a
    292 system has been invented which ties together the definition of a new
    293 opaque record type with the definitions of its getter and setter.
    294 This is considered outside the scope of this SRFI.
    295 
    296 
    297 Considerations when using as a library
    298 --------------------------------------
    299 
    300 The intent of this SRFI is to encourage Scheme systems to extend their
    301 standard library in accordance with the above specification.  On the
    302 meanwhile, the sample implementation can be used as a separate
    303 library, but certain considerations apply.
    304 
    305 The `define-record-type` export of the library conflicts with the one
    306 in `(scheme base)`, so either has to be renamed, or more typically,
    307 the one from `(scheme base)` excluded.
    308 
    309 Record types not defined with the `define-record-type` exported by
    310 this library won't work with `ref`, `ref*`, or their setters.
    311 
    312 This problem does not apply to implementations supporting inspection
    313 of records and record types.
    314 
    315 
    316 Implementation
    317 --------------
    318 
    319 A sample implementation as a library is found in the version control
    320 repository of this SRFI.
    321 
    322 It might be desirable for Scheme systems to offer a more efficient
    323 `type-of` procedure than the one used in this implementation, which in
    324 the worst case consumes linear time with regard to the number of types
    325 (including every record type) within the system, albeit with a very
    326 small constant factor: one call to each type predicate.
    327 
    328 
    329 Acknowledgments
    330 ---------------
    331 
    332 Thanks to Jorgen Schäfer for inspiring me to write this SRFI and
    333 making the initial suggestion for the `ref` procedure and ternary
    334 `set!` syntax, as well as providing continuous input.
    335 
    336 The `ref*` procedure with its `~` synonym and SRFI-17 setter (which
    337 replaced the initially considered ternary `set!` syntax) seems to have
    338 first appeared in Gauche.  Thanks to Shiro Kawai and Issac Trotts:
    339 <http://blog.practical-scheme.net/gauche/20100428-shorter-names>
    340 
    341 Thanks to Evan Hanson for the idea of using a throw-away `define` in
    342 the expansion of `define-record-type` so as not to disturb a sequence
    343 of internal definitions.
    344 
    345 Thanks to Vincent St-Amour, Eli Barzilay, and others in the Racket IRC
    346 channel for raising my awareness against action-at-a-distance bugs
    347 that might result from abuse of the imperative
    348 `register-getter-with-setter!`.
    349 
    350 Thanks also to everyone else on the discussion mailing list for their
    351 input.
    352 
    353 
    354 Copyright and license
    355 ---------------------
    356 
    357 Copyright (C) Taylan Ulrich Bayırlı/Kammer (2015). All Rights Reserved.
    358 
    359 Permission is hereby granted, free of charge, to any person obtaining
    360 a copy of this software and associated documentation files (the
    361 "Software"), to deal in the Software without restriction, including
    362 without limitation the rights to use, copy, modify, merge, publish,
    363 distribute, sublicense, and/or sell copies of the Software, and to
    364 permit persons to whom the Software is furnished to do so, subject to
    365 the following conditions:
    366 
    367 The above copyright notice and this permission notice shall be
    368 included in all copies or substantial portions of the Software.
    369 
    370 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    371 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    372 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    373 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    374 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    375 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    376 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.