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.