guile-srfi-123

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit a7fb6baf5d2e8b468345efb7306d1b5ccb15a2a8
parent f9ea4ea43b07b853d9ac32ae37b3c5f9d7d1ad60
Author: Arthur A. Gleckler <arthurgleckler@users.noreply.github.com>
Date:   Sun, 16 Aug 2015 19:46:12 -0700

Merge pull request #1 from TaylanUB/master

Incorporate Taylan's changes for second draft.
Diffstat:
Dgeneric-ref-set.body.scm | 143-------------------------------------------------------------------------------
Dgeneric-ref-set.sld | 11-----------
Mindex.html | 4++--
Msrfi-123.html | 63+++++++++++++++++++++++++++++++++++++++------------------------
Msrfi-123.md | 169+++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Asrfi/123.body.scm | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrfi/123.sld | 16++++++++++++++++
7 files changed, 386 insertions(+), 245 deletions(-)

diff --git a/generic-ref-set.body.scm b/generic-ref-set.body.scm @@ -1,143 +0,0 @@ -;;; generic-ref-set --- Generic accessor and modifier operators. - -;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com> - -;; 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. - -;;; Helpers - -(define-syntax push! - (syntax-rules () - ((_ <list-var> <x>) - (set! <list-var> (cons <x> <list-var>))))) - -(define (alist->hashtable alist) - (let ((table (make-eqv-hashtable 100))) - (for-each (lambda (entry) - (hashtable-set! table (car entry) (cdr entry))) - alist) - table)) - -;;; Main - -(define ref - (case-lambda - ((object field) - (let ((getter (lookup-getter object)) - (sparse? (sparse-type? object))) - (if sparse? - (let* ((not-found (cons #f #f)) - (result (getter object field not-found))) - (if (eqv? result not-found) - (error "Object has no entry for field." object field) - result)) - (getter object field)))) - ((object field default) - (let ((getter (lookup-getter object))) - (getter object field default))))) - -(define-syntax set! - (syntax-rules () - ((set! <place> <expression>) - (%set! <place> <expression>)) - ((set! <object> <field> <value>) - (let* ((object <object>) - (setter (lookup-setter object))) - (setter object <field> <value>))))) - -(set! (setter ref) (lambda (object field value) (set! object field value))) - -(define (lookup-getter object) - (or (hashtable-ref getter-table (type-of object) #f) - (error "No generic getter for object's type." object))) - -(define (lookup-setter object) - (or (hashtable-ref setter-table (type-of object) #f) - (error "No generic setter for object's type." object))) - -(define (sparse-type? object) - (memv (type-of object) sparse-types)) - -(define (type-of object) - (find (lambda (pred) (pred object)) type-list)) - -(define getter-table - (alist->hashtable - (list (cons bytevector? bytevector-u8-ref) - (cons hashtable? hashtable-ref) - (cons pair? list-ref) - (cons string? string-ref) - (cons vector? vector-ref)))) - -(define setter-table - (alist->hashtable - (list (cons bytevector? bytevector-u8-set!) - (cons hashtable? hashtable-set!) - (cons pair? list-set!) - (cons string? string-set!) - (cons vector? vector-set!)))) - -(define sparse-types - (list hashtable?)) - -(define type-list - (list boolean? bytevector? char? eof-object? hashtable? null? number? pair? - port? procedure? string? symbol? vector?)) - -(define-syntax define-record-type - (syntax-rules () - ((_ <name> <constructor> <pred> <field> ...) - (begin - (%define-record-type <name> <constructor> <pred> <field> ...) - (push! type-list <pred>) - (register-record-getter <pred> <field> ...) - (register-record-setter <pred> <field> ...))))) - -(define-syntax register-record-getter - (syntax-rules () - ((_ <pred> (<field> <getter> . <rest>) ...) - (let ((getters (alist->hashtable (list (cons '<field> <getter>) ...)))) - (define (getter record field) - (let ((getter (or (ref getters field #f) - (error "No such field of record." record field)))) - (getter record field))) - (set! getter-table <pred> getter))))) - -(define-syntax register-record-setter - (syntax-rules () - ((_ . <rest>) - (%register-record-setter () . <rest>)))) - -(define-syntax %register-record-setter - (syntax-rules () - ((_ <setters> <pred> (<field> <getter>) . <rest>) - (%register-record-setter <setters> <pred> . <rest>)) - ((_ <setters> <pred> (<field> <getter> <setter>) . <rest>) - (%register-record-setter ((<field> <setter>) . <setters>) <pred> . <rest>)) - ((_ ((<field> <setter>) ...) <pred>) - (let ((setters (alist->hashtable (list (cons '<field> <setter>) ...)))) - (define (setter record field value) - (let ((setter (or (ref setters field #f) - (error "No such assignable field of record." - record field)))) - (setter record value))) - (set! setter-table <pred> setter))))) - -;;; generic-ref-set.body.scm ends here diff --git a/generic-ref-set.sld b/generic-ref-set.sld @@ -1,11 +0,0 @@ -(define-library (generic-ref-set) - (export - ref set! define-record-type (rename ref $bracket-apply$)) - (import - (rename (except (scheme base) set!) - (define-record-type %define-record-type)) - (scheme case-lambda) - (r6rs hashtables) - (srfi 1) - (rename (srfi 17) (set! %set!))) - (include "generic-ref-set.body.scm")) diff --git a/index.html b/index.html @@ -3,6 +3,7 @@ <head> <title>Generic accessor and modifier operators</title> <link href="../admin.css" rel="stylesheet"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> @@ -35,4 +36,4 @@ <hr> <address><a href="mailto:srfi minus editors at srfi dot schemers dot org">The SRFI Editors</a></address> </body> -</html> -\ No newline at end of file +</html> diff --git a/srfi-123.html b/srfi-123.html @@ -26,22 +26,26 @@ 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>The operators are specified to work on bytevectors, R6RS hashtables, lists, strings, vectors, and all record types. Some notes on specific types:</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 non-opaque record types. (R6RS and SRFI-99 can produce opaque record types; SRFI-9 and R7RS cannot.) 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> <pre><code>(define bv (bytevector 0 1 2 3)) (ref bv 2) ;=&gt; 2 (set! bv 2 5) (ref bv 2) ;=&gt; 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 &quot;foo&quot; &#39;not-found) ;=&gt; not-found (set! table &quot;foo&quot; &quot;Foobar.&quot;) (ref table &quot;foo&quot;) ;=&gt; &quot;Foobar.&quot; (ref table &quot;bar&quot;) ;error: Object has no entry for field.</code></pre></li> -<li><p>Lists are supported by testing the given object for a pair. Pairs themselves are senseless to support because <code>(set! pair car value)</code> contains the same number of words as <code>(set-car! pair value)</code>. In the <code>ref</code> equivalent, it even contains one word more: <code>(ref pair car)</code> vs. <code>(car pair)</code>.</p> -<pre><code>(ref &#39;(a b c . d) 2) ;=&gt; c</code></pre></li> +<li><p>When a pair is encountered, the field argument may be the procedures <code>car</code> or <code>cdr</code>, or an integer index indicating the pair should be viewed as the head of a list.</p> +<pre><code>(ref &#39;(a b c . d) cdr) ;=&gt; (b c . d) +(ref &#39;(a b c . d) 2) ;=&gt; c</code></pre></li> <li><p>For records, the accepted values for the <code>field</code> parameter are symbols corresponding to the record type's field names. The overhead involved in looking up the correct accessor of modifier falls under the same rationale as other kinds of overhead involved with this SRFI.</p> <pre><code>(define-record-type &lt;foo&gt; (make-foo a b) foo? (a foo-a set-foo-a!) @@ -50,26 +54,16 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the (ref foo &#39;a) ;=&gt; 0 (set! foo &#39;b 2) ;error: No such assignable field of record.</code></pre></li> </ul> -<p>Alists are unfortunately impossible 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.)</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 &quot;empty&quot; 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&#39;s car to bar. -(set! (car foo) bar quux) ;Sets the bar field of the object in - ;foo&#39;s car to quux.</code></pre> -<p>Additionally, if SRFI-17 is supported, the <code>ref</code> procedure's &quot;setter&quot; 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> +<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> +<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]} ;=&gt; 3 {vec[2] := 4} {vec[1] + vec[2]} ;=&gt; 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> @@ -84,19 +78,40 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the <pre><code>(ref &#39;(0 1 2) 3 &#39;default) ;error: list-ref: Too many arguments. ;Unless the implementation&#39;s list-ref ;does something else.</code></pre> -<p>Valid types for <code>object</code> are: bytevectors, hashtables, pairs, strings, vectors, and all record types. 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>field</code> depend on the type of <code>object</code>. For bytevectors, hashtables, strings, and 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>Valid types for <code>object</code> are: bytevectors, hashtables, pairs, strings, vectors, non-opaque 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>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> +<ul> +<li><code>(register-getter-with-setter! type getter sparse?)</code> (procedure)</li> +</ul> +<p>Registers a new type/getter/setter triple for the dynamic dispatch. <code>Type</code> is a type predicate, <code>getter</code> is a procedure that has a setter associated with it (as returned by the <code>getter-with-setter</code> procedure of SRFI-17), and <code>sparse?</code> is a Boolean indicating whether the type is a sparse type (see <code>ref</code> specification).</p> <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 their standard library in accordance with the above specification. On the meanwhile, the reference implementation 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> +<p>The <code>define-record-type</code> exported by this library expands to a record type definition followed with a command, essentially eliminating the &quot;definition&quot; status of <code>define-record-type</code>. This means, for example, that you can't use it more than once (only at the end) within the internal definitions sequence of a body. It's a rare use-case, but if you need it, you can nest each additional <code>define-record-type</code> use in a further <code>(let () ...)</code>. It works fine in the top-level, since there definitions and commands can be interspersed.</p> <h2 id="implementation">Implementation</h2> <p>A reference implementation as a library is found in the version control repository of this SRFI.</p> <p>It might be desirable for Scheme systems to offer a more efficient <code>type-of</code> procedure than the one used in this implementation, which in the worst case consumes linear time with regard to the number of types (including every record type) within the system, albeit with a very small constant factor: one call to each type predicate.</p> diff --git a/srfi-123.md b/srfi-123.md @@ -20,10 +20,11 @@ subscribe to the list, follow [these instructions](http://srfi.schemers.org/srfi-list-subscribe.html). You can access previous messages via the mailing list [archive](http://srfi-email.schemers.org/srfi-123). - + - Received: 2015/8/14 - Draft #1 published: 2015/8/15 + Abstract -------- @@ -51,14 +52,27 @@ 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. The operators are specified to work on bytevectors, R6RS hashtables, -lists, strings, vectors, and all record types. Some notes on specific -types: +lists/pairs, strings, vectors, non-opaque record types, and SRFI-4 +vectors if present. (R6RS and SRFI-99 can produce opaque record +types; SRFI-9 and R7RS cannot.) Some notes on specific types: - For bytevectors, 8-bit unsigned integer operations are assumed. There is no obvious way to incorporate other bytevector operations @@ -74,7 +88,9 @@ 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)) @@ -84,15 +100,14 @@ types: (ref table "bar") ;error: Object has no entry for field. ``` -- Lists are supported by testing the given object for a pair. Pairs - themselves are senseless to support because `(set! pair car value)` - contains the same number of words as `(set-car! pair value)`. In - the `ref` equivalent, it even contains one word more: - `(ref pair car)` vs. `(car pair)`. +- When a pair is encountered, the field argument may be the procedures + `car` or `cdr`, or an integer index indicating the pair should be + viewed as the head of a list. - ``` + ```` + (ref '(a b c . d) cdr) ;=> (b c . d) (ref '(a b c . d) 2) ;=> c - ``` + ```` - For records, the accepted values for the `field` parameter are symbols corresponding to the record type's field names. The @@ -109,56 +124,39 @@ types: (set! foo 'b 2) ;error: No such assignable field of record. ``` -Alists are unfortunately impossible to support due to the lack of a -reliable `alist?` predicate. (It's ambiguous in that every alist is -also a list, and any list may coincidentally have the structure of an -alist.) - -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) +Alists are difficult to support due to the lack of a reliable `alist?` +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 `alist-set!` is an exceedingly rare operation. +(Prepending an entry to the front, possibly hiding another entry with +the same key, is more common.) -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 ------------- @@ -188,18 +186,34 @@ error. ;does something else. Valid types for `object` are: bytevectors, hashtables, pairs, strings, -vectors, and all record types. Only hashtables are a sparse type. -Implementations are encouraged to expand this list of types with any -non-standard types they support. +vectors, non-opaque 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. Valid types for `field` depend on the type of `object`. For -bytevectors, hashtables, strings, and vectors, refer 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. +bytevectors, hashtables, strings, vectors, and SRFI-4 vectors, refer +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. + +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+ ...) -If SRFI-17 is supported, then the `ref` procedure has the following -setter: `(lambda (object field value) (set! object field value))` +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) @@ -211,16 +225,32 @@ 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)))) + +- `(register-getter-with-setter! type getter sparse?)` (procedure) + +Registers a new type/getter/setter triple for the dynamic dispatch. +`Type` is a type predicate, `getter` is a procedure that has a setter +associated with it (as returned by the `getter-with-setter` procedure +of SRFI-17), and `sparse?` is a Boolean indicating whether the type is +a sparse type (see `ref` specification). 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. +The intent of this SRFI is to encourage Scheme systems to extend their +standard library in accordance with the above specification. On the +meanwhile, the reference implementation 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 @@ -229,6 +259,15 @@ more typically, the ones from `(scheme base)` excluded. Record types not defined with the `define-record-type` exported by this library won't work with `ref` and `set!`. +The `define-record-type` exported by this library expands to a record +type definition followed with a command, essentially eliminating the +"definition" status of `define-record-type`. This means, for example, +that you can't use it more than once (only at the end) within the +internal definitions sequence of a body. It's a rare use-case, but if +you need it, you can nest each additional `define-record-type` use in +a further `(let () ...)`. It works fine in the top-level, since there +definitions and commands can be interspersed. + Implementation -------------- diff --git a/srfi/123.body.scm b/srfi/123.body.scm @@ -0,0 +1,225 @@ +;;; generic-ref-set --- Generic accessor and modifier operators. + +;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com> + +;; 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. + +;;; Helpers + +(define-syntax push! + (syntax-rules () + ((_ <list-var> <x>) + (set! <list-var> (cons <x> <list-var>))))) + +(define (alist->hashtable alist) + (let ((table (make-eqv-hashtable 100))) + (for-each (lambda (entry) + (hashtable-set! table (car entry) (cdr entry))) + alist) + table)) + +(define (pair-ref pair key) + (cond + ((eqv? car key) + (car pair)) + ((eqv? cdr key) + (cdr pair)) + (else + (list-ref pair key)))) + +(define (pair-set! pair key value) + (cond + ((eqv? car key) + (set-car! pair value)) + ((eqv? cdr key) + (set-cdr! pair value)) + (else + (list-set! pair key value)))) + +;;; SRFI-4 support + +(cond-expand + ((library (srfi 4)) + (define srfi-4-getters + (list (cons s8vector? s8vector-ref) + (cons u8vector? u8vector-ref) + (cons s16vector? s16vector-ref) + (cons u16vector? u16vector-ref) + (cons s32vector? s32vector-ref) + (cons u32vector? u32vector-ref) + (cons s64vector? s64vector-ref) + (cons u64vector? u64vector-ref))) + (define srfi-4-setters + (list (cons s8vector? s8vector-set!) + (cons u8vector? u8vector-set!) + (cons s16vector? s16vector-set!) + (cons u16vector? u16vector-set!) + (cons s32vector? s32vector-set!) + (cons u32vector? u32vector-set!) + (cons s64vector? s64vector-set!) + (cons u64vector? u64vector-set!))) + (define srfi-4-types + (list s8vector? u8vector? s16vector? u16vector? s32vector? u32vector? + s64vector? u64vector?))) + (else + (define srfi-4-getters '()) + (define srfi-4-setters '()) + (define srfi-4-types '()))) + +;;; Main + +(define %ref + (case-lambda + ((object field) + (let ((getter (lookup-getter object)) + (sparse? (sparse-type? object))) + (if sparse? + (let* ((not-found (cons #f #f)) + (result (getter object field not-found))) + (if (eqv? result not-found) + (error "Object has no entry for field." object field) + result)) + (getter object field)))) + ((object field default) + (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>) + (%set! <place> <expression>)) + ((set! <object> <field> <value>) + (let* ((object <object>) + (setter (lookup-setter object))) + (setter 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) + (error "No generic getter for object's type." object))) + +(define (lookup-setter object) + (or (hashtable-ref setter-table (type-of object) #f) + (error "No generic setter for object's type." object))) + +(define (sparse-type? object) + (memv (type-of object) sparse-types)) + +(define (type-of object) + (find (lambda (pred) (pred object)) type-list)) + +(define getter-table + (alist->hashtable + (append + (list (cons bytevector? bytevector-u8-ref) + (cons hashtable? hashtable-ref) + (cons pair? pair-ref) + (cons string? string-ref) + (cons vector? vector-ref)) + srfi-4-getters))) + +(define setter-table + (alist->hashtable + (append + (list (cons bytevector? bytevector-u8-set!) + (cons hashtable? hashtable-set!) + (cons pair? pair-set!) + (cons string? string-set!) + (cons vector? vector-set!)) + srfi-4-setters))) + +(define sparse-types + (list hashtable?)) + +(define type-list + (append + (list boolean? bytevector? char? eof-object? hashtable? null? number? pair? + port? procedure? string? symbol? vector?) + srfi-4-types)) + +(define (register-getter-with-setter! type getter sparse?) + (push! type-list type) + (set! getter-table type getter) + (set! setter-table type (setter getter)) + (when sparse? + (push! sparse-types type))) + +(define-syntax define-record-type + (syntax-rules () + ((_ <name> <constructor> <pred> <field> ...) + (begin + (%define-record-type <name> <constructor> <pred> <field> ...) + (register-getter-with-setter! + <pred> + (getter-with-setter (record-getter <field> ...) + (record-setter <field> ...)) + #f))))) + +(define-syntax record-getter + (syntax-rules () + ((_ (<field> <getter> . <rest>) ...) + (let ((getters (alist->hashtable (list (cons '<field> <getter>) ...)))) + (lambda (record field) + (let ((getter (or (ref getters field #f) + (error "No such field of record." record field)))) + (getter record field))))))) + +(define-syntax record-setter + (syntax-rules () + ((_ . <rest>) + (%record-setter () . <rest>)))) + +(define-syntax %record-setter + (syntax-rules () + ((_ <setters> (<field> <getter>) . <rest>) + (%record-setter <setters> . <rest>)) + ((_ <setters> (<field> <getter> <setter>) . <rest>) + (%record-setter ((<field> <setter>) . <setters>) . <rest>)) + ((_ ((<field> <setter>) ...)) + (let ((setters (alist->hashtable (list (cons '<field> <setter>) ...)))) + (lambda (record field value) + (let ((setter (or (ref setters field #f) + (error "No such assignable field of record." + record field)))) + (setter record value))))))) + +;;; generic-ref-set.body.scm ends here diff --git a/srfi/123.sld b/srfi/123.sld @@ -0,0 +1,16 @@ +(define-library (srfi 123) + (export + 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)) + (import (srfi 4))) + (else)) + (include "123.body.scm"))