commit a84b34e15756779c97367b221e412e854c49baef
parent e2da3d0d0bfd1cec8a7f7f0024c5bdfff17e3506
Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
Date: Sun, 16 Aug 2015 19:53:59 +0200
Add ref* to the specification.
Diffstat:
4 files changed, 111 insertions(+), 65 deletions(-)
diff --git a/srfi-123.html b/srfi-123.html
@@ -26,7 +26,10 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the
<p>In some types of code-bases, accessing and modifying fields of certain collection objects (such as vectors, hashtables, or records) are ubiquitous operations. Standard Scheme APIs contain verbose procedure names specialized for each data type, which may become very tedious to type, and clutter the code.</p>
<p>In contrast, most other languages offer very short and simple syntax for such operations, such as square bracket and dotted notation: <code>object[field]</code> and <code>object.field</code> for access; <code>object[field] = value</code> and <code>object.field = value</code> for modification.</p>
<p>To accommodate, we define a pair of generic accessor and modifier operators that work through type-based dynamic dispatch: <code>(ref object field)</code> for access and <code>(set! object field value)</code> for modification.</p>
-<p>We believe the overhead involved in this is negligible in most code-bases, and furthermore a programmer can always fall back to the type-specific accessor and modifier procedures in performance-critical sections of code.</p>
+<p>We also define a variant of <code>ref</code> that accepts chained field arguments: <code>(ref* object field1 field2 ...)</code>.</p>
+<p>And define <code>~</code> as a synonym to <code>ref*</code>, as well as define an SRFI-17 setter for it: <code>(set! (~ object field1 field2 ...) value)</code>.</p>
+<p>Note that this makes <code>(set! object field value)</code> redundant in the strict sense, since it is served by <code>(set! (~ object field) value)</code>. We decide to keep this syntax anyhow since it is expected to be a very common use-case, for which the additional parentheses and tilde may as well be saved.</p>
+<p>We believe the overhead involved in the dynamic dispatch is negligible in most cases, and furthermore a programmer can always fall back to type-specific accessor and modifier procedures in performance-critical sections of code.</p>
<p>The operators are specified to work on bytevectors, R6RS hashtables, lists/pairs, strings, vectors, and all record types. Some notes on specific types:</p>
<ul>
<li><p>For bytevectors, 8-bit unsigned integer operations are assumed. There is no obvious way to incorporate other bytevector operations into the generalized API, and a programmer is most likely to have single-byte operations in mind when using a generalized API on bytevectors.</p>
@@ -34,7 +37,7 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the
(ref bv 2) ;=> 2
(set! bv 2 5)
(ref bv 2) ;=> 5</code></pre></li>
-<li><p>For hashtables, the <code>ref</code> operator takes an optional <code>default</code> argument whose semantics is akin to <code>hashtable-ref</code>.</p>
+<li><p>For hashtables, the <code>ref</code> operator takes an optional <code>default</code> argument whose semantics is akin to <code>hashtable-ref</code>. (This is not possible with <code>ref*</code>; it will always behave as when no default argument is passed.)</p>
<pre><code>(define table (make-eqv-hashtable))
(ref table "foo" 'not-found) ;=> not-found
(set! table "foo" "Foobar.")
@@ -52,25 +55,15 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the
(set! foo 'b 2) ;error: No such assignable field of record.</code></pre></li>
</ul>
<p>Alists are difficult to support due to the lack of a reliable <code>alist?</code> predicate. (It's ambiguous in that every alist is also a list, and any list may coincidentally have the structure of an alist.) It was considered to support non-integer keyed alists as a special case, but this would lead to silent code breakage when a programmer forgot about the API inconsistency and exchanged a non-integer key for an integer key in existing code. It was also considered to drop list support in favor of alist support, but that idea discarded as well because the hypothetical <code>alist-set!</code> is an exceedingly rare operation. (Prepending an entry to the front, possibly hiding another entry with the same key, is more common.)</p>
-<p>A <code>ref*</code> procedure taking an arbitrary number of <code>field</code> arguments and walking through several collections was considered, but deemed sub-optimal because it doesn't play well with collections that may have "empty" fields (e.g. hashtables), and usually one doesn't walk through deep structures at once, and instead binds intermediate results to a variable. Nevertheless, it is trivial to define if desired:</p>
-<pre><code>(define (ref* object field . fields)
- (if (null? fields)
- (ref object field)
- (apply ref* (ref object field) fields)</code></pre>
-<p>This might be a better candidate for SRFI-105's <code>$bracket-apply$</code> than regular <code>ref</code>.</p>
-<h2 id="integration-with-srfi-17-and-srfi-105">Integration with SRFI-17 and SRFI-105</h2>
-<p>The <code>set!</code> operator in this SRFI does not conflict with the one in SRFI-17. The reference implementation extends the SRFI-17 <code>set!</code> and thus supports the functionality of both SRFI-17 and the one described here.</p>
-<pre><code>(set! (car foo) bar) ;Sets foo's car to bar.
-(set! (car foo) bar quux) ;Sets the bar field of the object in
- ;foo's car to quux.</code></pre>
-<p>Additionally, if SRFI-17 is supported, the <code>ref</code> procedure's "setter" may be defined as: <code>(lambda (object field value) (set! object field value))</code>. This is uninteresting in its own right, but can yield an interesting combination with SRFI-105. In code that already uses SRFI-105 heavily, a programmer may define <code>$bracket-apply$</code> as a synonym to <code>ref</code>, define <code>:=</code> as a synonym to <code>set!</code>, and then use the following syntax: <code>{object[field] := value}</code>.</p>
+<h2 id="integration-with-srfi-105">Integration with SRFI-105</h2>
+<p>The <code>ref*</code> procedure is a good candidate for SRFI-105's <code>$bracket-apply$</code>. Indeed the reference implementation exports <code>$bracket-apply$</code> as a synonym to <code>ref*</code>. In code that already uses SRFI-105 heavily, a programmer may additionally define <code>:=</code> as a synonym to <code>set!</code>, and then use the following syntax: <code>{object[field] := value}</code>.</p>
<pre><code>#!curly-infix
(import (rename (only (scheme base) set!) (set! :=)))
-(define $bracket-apply$ ref)
(define vec (vector 0 1 2 3))
{vec[1] + vec[2]} ;=> 3
{vec[2] := 4}
{vec[1] + vec[2]} ;=> 5</code></pre>
+<p>The square brackets accept a chain of fields, since they have the semantics of <code>ref*</code>: <code>{matrix[i j]}</code>.</p>
<h2 id="specification">Specification</h2>
<ul>
<li><code>(ref object field)</code> (procedure)</li>
@@ -85,17 +78,33 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the
<pre><code>(ref '(0 1 2) 3 'default) ;error: list-ref: Too many arguments.
;Unless the implementation's list-ref
;does something else.</code></pre>
-<p>Valid types for <code>object</code> are: bytevectors, hashtables, pairs, strings, vectors, all record types, and SRFI-4 vectors if present. Only hashtables are a sparse type. Implementations are encouraged to expand this list of types with any non-standard types they support.</p>
+<p>Valid types for <code>object</code> are: bytevectors, hashtables, pairs, strings, vectors, all record types, and SRFI-4 vectors if present. Only hashtables are a sparse type. Implementations are encouraged to expand this list of types with any further types they support.</p>
<p>Valid types for <code>field</code> depend on the type of <code>object</code>. For bytevectors, hashtables, strings, vectors, and SRFI-4 vectors, refer to their respective <code>*-ref</code> procedures. For pairs, refer to <code>list-ref</code>. For records, symbols that correspond with the record type's field names are allowed.</p>
-<p>If SRFI-17 is supported, then the <code>ref</code> procedure has the following setter: <code>(lambda (object field value) (set! object field value))</code></p>
+<p>The <code>ref</code> procedure has the following SRFI-17 setter:</p>
+<pre><code>(lambda (object field value) (set! object field value))</code></pre>
+<ul>
+<li><code>(ref* object field field* ...)</code> (procedure)</li>
+<li><code>(~ object field field* ...)</code></li>
+</ul>
+<p>The semantics is of this procedure is as follows:</p>
+<pre><code>(ref* object field) = (ref object field)
+(ref* object field field+ ...) = (ref* (ref object field) field+ ...)</code></pre>
+<p>It has the following SRFI-17 setter:</p>
+<pre><code>(define (set!* object field rest0 . rest)
+ (if (null? rest)
+ (set! object field rest0)
+ (apply set!* (ref object field) rest0 rest)))</code></pre>
<ul>
<li><code>(set! object field value)</code> (syntax)</li>
</ul>
<p>Sets the value for <code>field</code> in <code>object</code> to <code>value</code>.</p>
<p>Valid types for <code>object</code> and <code>field</code> are the same as in the <code>ref</code> procedure. Valid types for <code>value</code> are whatever values <code>object</code> may hold in <code>field</code>.</p>
-<p>Note: This operator is only a syntax keyword because it overloads the normal <code>set!</code> syntax. An equivalent procedure is trivial to define: <code>(lambda (object field value) (set! object field value))</code>.</p>
+<p>Note: This operator is only a syntax keyword because it overloads the normal <code>set!</code> syntax. An equivalent procedure is trivial to define: <code>(lambda (object field value) (set! object field value))</code>. This procedure is in fact the setter of <code>ref</code>, so it can be accessed as <code>(setter ref)</code> when needed.</p>
+<p>The corresponding <code>set!*</code> procedure is left out, but can be accessed as <code>(setter ref*)</code> when needed.</p>
+<pre><code>(define (store-item! field-chain value)
+ (apply (setter ref*) the-store (append field-chain (list value))))</code></pre>
<h2 id="considerations-when-using-as-a-library">Considerations when using as a library</h2>
-<p>The intent of this SRFI is to encourage Scheme systems to extend the semantics of their default <code>set!</code> operator in line with this SRFI. On the meanwhile, it can be used as a library, but certain considerations apply.</p>
+<p>The intent of this SRFI is to encourage Scheme systems to extend the semantics of their default <code>set!</code> operator in line with this SRFI, and add <code>ref</code> and <code>ref*</code> to their standard library. On the meanwhile, it can be used as a separate library, but certain considerations apply.</p>
<p>The <code>set!</code> and <code>define-record-type</code> exports of the library conflict with the ones in <code>(scheme base)</code>, so either have to be renamed, or more typically, the ones from <code>(scheme base)</code> excluded.</p>
<p>Record types not defined with the <code>define-record-type</code> exported by this library won't work with <code>ref</code> and <code>set!</code>.</p>
<h2 id="implementation">Implementation</h2>
diff --git a/srfi-123.md b/srfi-123.md
@@ -24,6 +24,7 @@ You can access previous messages via the mailing list
- Received: 2015/8/14
- Draft #1 published: 2015/8/15
+
Abstract
--------
@@ -51,8 +52,20 @@ To accommodate, we define a pair of generic accessor and modifier
operators that work through type-based dynamic dispatch: `(ref object
field)` for access and `(set! object field value)` for modification.
-We believe the overhead involved in this is negligible in most
-code-bases, and furthermore a programmer can always fall back to the
+We also define a variant of `ref` that accepts chained field
+arguments: `(ref* object field1 field2 ...)`.
+
+And define `~` as a synonym to `ref*`, as well as define an SRFI-17
+setter for it: `(set! (~ object field1 field2 ...) value)`.
+
+Note that this makes `(set! object field value)` redundant in the
+strict sense, since it is served by `(set! (~ object field) value)`.
+We decide to keep this syntax anyhow since it is expected to be a very
+common use-case, for which the additional parentheses and tilde may as
+well be saved.
+
+We believe the overhead involved in the dynamic dispatch is negligible
+in most cases, and furthermore a programmer can always fall back to
type-specific accessor and modifier procedures in performance-critical
sections of code.
@@ -74,7 +87,9 @@ specific types:
```
- For hashtables, the `ref` operator takes an optional `default`
- argument whose semantics is akin to `hashtable-ref`.
+ argument whose semantics is akin to `hashtable-ref`. (This is not
+ possible with `ref*`; it will always behave as when no default
+ argument is passed.)
```
(define table (make-eqv-hashtable))
@@ -120,51 +135,27 @@ hypothetical `alist-set!` is an exceedingly rare operation.
(Prepending an entry to the front, possibly hiding another entry with
the same key, is more common.)
-A `ref*` procedure taking an arbitrary number of `field` arguments and
-walking through several collections was considered, but deemed
-sub-optimal because it doesn't play well with collections that may
-have "empty" fields (e.g. hashtables), and usually one doesn't walk
-through deep structures at once, and instead binds intermediate
-results to a variable. Nevertheless, it is trivial to define if
-desired:
-
- (define (ref* object field . fields)
- (if (null? fields)
- (ref object field)
- (apply ref* (ref object field) fields)
-This might be a better candidate for SRFI-105's `$bracket-apply$` than
-regular `ref`.
-
-
-Integration with SRFI-17 and SRFI-105
+Integration with SRFI-105
-------------------------------------
-The `set!` operator in this SRFI does not conflict with the one in
-SRFI-17. The reference implementation extends the SRFI-17 `set!` and
-thus supports the functionality of both SRFI-17 and the one described
-here.
-
- (set! (car foo) bar) ;Sets foo's car to bar.
- (set! (car foo) bar quux) ;Sets the bar field of the object in
- ;foo's car to quux.
-
-Additionally, if SRFI-17 is supported, the `ref` procedure's "setter"
-may be defined as: `(lambda (object field value) (set! object field
-value))`. This is uninteresting in its own right, but can yield an
-interesting combination with SRFI-105. In code that already uses
-SRFI-105 heavily, a programmer may define `$bracket-apply$` as a
-synonym to `ref`, define `:=` as a synonym to `set!`, and then use the
-following syntax: `{object[field] := value}`.
+The `ref*` procedure is a good candidate for SRFI-105's
+`$bracket-apply$`. Indeed the reference implementation exports
+`$bracket-apply$` as a synonym to `ref*`. In code that already uses
+SRFI-105 heavily, a programmer may additionally define `:=` as a
+synonym to `set!`, and then use the following syntax:
+`{object[field] := value}`.
#!curly-infix
(import (rename (only (scheme base) set!) (set! :=)))
- (define $bracket-apply$ ref)
(define vec (vector 0 1 2 3))
{vec[1] + vec[2]} ;=> 3
{vec[2] := 4}
{vec[1] + vec[2]} ;=> 5
+The square brackets accept a chain of fields, since they have the
+semantics of `ref*`: `{matrix[i j]}`.
+
Specification
-------------
@@ -204,8 +195,24 @@ to their respective `*-ref` procedures. For pairs, refer to
`list-ref`. For records, symbols that correspond with the record
type's field names are allowed.
-If SRFI-17 is supported, then the `ref` procedure has the following
-setter: `(lambda (object field value) (set! object field value))`
+The `ref` procedure has the following SRFI-17 setter:
+
+ (lambda (object field value) (set! object field value))
+
+- `(ref* object field field* ...)` (procedure)
+- `(~ object field field* ...)`
+
+The semantics is of this procedure is as follows:
+
+ (ref* object field) = (ref object field)
+ (ref* object field field+ ...) = (ref* (ref object field) field+ ...)
+
+It has the following SRFI-17 setter:
+
+ (define (set!* object field rest0 . rest)
+ (if (null? rest)
+ (set! object field rest0)
+ (apply set!* (ref object field) rest0 rest)))
- `(set! object field value)` (syntax)
@@ -217,16 +224,24 @@ hold in `field`.
Note: This operator is only a syntax keyword because it overloads the
normal `set!` syntax. An equivalent procedure is trivial to define:
-`(lambda (object field value) (set! object field value))`.
+`(lambda (object field value) (set! object field value))`. This
+procedure is in fact the setter of `ref`, so it can be accessed as
+`(setter ref)` when needed.
+
+The corresponding `set!*` procedure is left out, but can be accessed
+as `(setter ref*)` when needed.
+
+ (define (store-item! field-chain value)
+ (apply (setter ref*) the-store (append field-chain (list value))))
Considerations when using as a library
--------------------------------------
The intent of this SRFI is to encourage Scheme systems to extend the
-semantics of their default `set!` operator in line with this SRFI. On
-the meanwhile, it can be used as a library, but certain considerations
-apply.
+semantics of their default `set!` operator in line with this SRFI, and
+add `ref` and `ref*` to their standard library. On the meanwhile, it
+can be used as a separate library, but certain considerations apply.
The `set!` and `define-record-type` exports of the library conflict
with the ones in `(scheme base)`, so either have to be renamed, or
diff --git a/srfi/123.body.scm b/srfi/123.body.scm
@@ -85,7 +85,7 @@
;;; Main
-(define ref
+(define %ref
(case-lambda
((object field)
(let ((getter (lookup-getter object))
@@ -101,6 +101,11 @@
(let ((getter (lookup-getter object)))
(getter object field default)))))
+(define (%ref* object field . fields)
+ (if (null? fields)
+ (%ref object field)
+ (apply %ref* (%ref object field) fields)))
+
(define-syntax set!
(syntax-rules ()
((set! <place> <expression>)
@@ -110,7 +115,23 @@
(setter (lookup-setter object)))
(setter object <field> <value>)))))
-(set! (setter ref) (lambda (object field value) (set! object field value)))
+(define ref
+ (getter-with-setter
+ %ref
+ (lambda (object field value)
+ (set! object field value))))
+
+(define ref*
+ (getter-with-setter
+ %ref*
+ (rec (set!* object field rest0 . rest)
+ (if (null? rest)
+ (set! object field rest0)
+ (apply set!* (ref object field) rest0 rest)))))
+
+(define ~ ref*)
+
+(define $bracket-apply$ ref*)
(define (lookup-getter object)
(or (hashtable-ref getter-table (type-of object) #f)
diff --git a/srfi/123.sld b/srfi/123.sld
@@ -1,12 +1,13 @@
(define-library (srfi 123)
(export
- ref set! define-record-type (rename ref $bracket-apply$))
+ ref ref* ~ $bracket-apply$ set! define-record-type)
(import
(rename (except (scheme base) set!)
(define-record-type %define-record-type))
(scheme case-lambda)
(r6rs hashtables)
(srfi 1)
+ (srfi 31)
(rename (srfi 17) (set! %set!)))
(cond-expand
((library (srfi 4))