guile-srfi-123

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

commit cf2edff703fd65a3dc148c994362e5a28853cc33
parent feca026301746ec7674761325e73aef9f3b38424
Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
Date:   Sun, 16 Aug 2015 23:21:39 +0200

Get rid of ternary set!.

Diffstat:
Msrfi-123.html | 41+++++++++++++++--------------------------
Msrfi-123.md | 80++++++++++++++++++++++++++++++-------------------------------------------------
Msrfi/123.body.scm | 19+++++++------------
Msrfi/123.sld | 9++++++---
4 files changed, 58 insertions(+), 91 deletions(-)

diff --git a/srfi-123.html b/srfi-123.html @@ -23,26 +23,25 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the <li>Draft #2 published: 2015/8/16</li> </ul> <h2 id="abstract">Abstract</h2> -<p>Lisp dialects including Scheme have traditionally lacked short, simple, generic syntax for accessing and modifying the fields of arbitrary &quot;collection&quot; objects. We fill this gap for Scheme by defining generalized <code>ref</code> and <code>set!</code> operators.</p> +<p>Lisp dialects including Scheme have traditionally lacked short, simple, generic syntax for accessing and modifying the fields of arbitrary &quot;collection&quot; objects. We fill this gap for Scheme by defining generalized accessors, and an associated SRFI-17 setter.</p> <h2 id="rationale">Rationale</h2> <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 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>To accommodate, we define a pair of generic accessor operators that work through type-based dynamic dispatch: <code>(ref object field)</code>, and <code>(ref* object field1 field2 ...)</code> for chained access.</p> +<p>We define <code>~</code> as a synonym to <code>ref*</code>, and define an SRFI-17 setter for it: <code>(set! (~ object field1 field2 ...) value)</code>.</p> +<p>Plain <code>ref</code>, instead of allowing chaining, takes an optional <code>default</code> argument for objects such as hashtables: <code>(ref table key default)</code>.</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, 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:</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) +(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>. (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;) +(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>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> @@ -54,7 +53,7 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the (b foo-b)) (define foo (make-foo 0 1)) (ref foo &#39;a) ;=&gt; 0 -(set! foo &#39;b 2) ;error: No such assignable field of record.</code></pre></li> +(set! (~ foo &#39;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> <h2 id="integration-with-srfi-105">Integration with SRFI-105</h2> @@ -82,8 +81,6 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the ;does something else.</code></pre> <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> @@ -91,34 +88,26 @@ class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the <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>. 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> +<p>It has an associated SRFI-17 setter, which does the expected thing:</p> +<pre><code>(set! (~ obj f1 f2 f3) value)</code></pre> +<p>changes the value that would be returned from <code>(~ obj f1 f2 f3)</code> to <code>value</code>. Note that this procedure 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> +<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, 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 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> export of the library conflicts with the one in <code>(scheme base)</code>, so either has to be renamed, or more typically, the one 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>ref*</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> <h2 id="acknowledgments">Acknowledgments</h2> -<p>Original idea and some input during design by Jorgen Schäfer.</p> +<p>Thanks to Jorgen Schäfer for inspiring me to write this SRFI and making the initial suggestion for the <code>ref</code> procedure and ternary <code>set!</code> syntax.</p> +<p>The <code>ref*</code> procedure with its <code>~</code> synonym and SRFI-17 setter (which replaced the initially considered ternary <code>set!</code> syntax) seems to have first appeared in Gauche. Thanks to Shiro Kawai: <a href="http://blog.practical-scheme.net/gauche/20100428-shorter-names" class="uri">http://blog.practical-scheme.net/gauche/20100428-shorter-names</a></p> <h2 id="copyright-and-license">Copyright and license</h2> <p>Copyright (C) Taylan Ulrich Bayırlı/Kammer (2015). All Rights Reserved.</p> <p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the &quot;Software&quot;), 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:</p> diff --git a/srfi-123.md b/srfi-123.md @@ -32,7 +32,7 @@ Abstract Lisp dialects including Scheme have traditionally lacked short, simple, generic syntax for accessing and modifying the fields of arbitrary "collection" objects. We fill this gap for Scheme by -defining generalized `ref` and `set!` operators. +defining generalized accessors, and an associated SRFI-17 setter. Rationale @@ -49,21 +49,15 @@ for such operations, such as square bracket and dotted notation: `object[field]` and `object.field` for access; `object[field] = value` and `object.field = value` for modification. -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. +To accommodate, we define a pair of generic accessor operators that +work through type-based dynamic dispatch: `(ref object field)`, and +`(ref* object field1 field2 ...)` for chained access. -We also define a variant of `ref` that accepts chained field -arguments: `(ref* object field1 field2 ...)`. +We define `~` as a synonym to `ref*`, and define an SRFI-17 setter for +it: `(set! (~ object field1 field2 ...) value)`. -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. +Plain `ref`, instead of allowing chaining, takes an optional `default` +argument for objects such as hashtables: `(ref table key default)`. We believe the overhead involved in the dynamic dispatch is negligible in most cases, and furthermore a programmer can always fall back to @@ -84,7 +78,7 @@ types; SRFI-9 and R7RS cannot.) Some notes on specific types: ``` (define bv (bytevector 0 1 2 3)) (ref bv 2) ;=> 2 - (set! bv 2 5) + (set! (~ bv 2) 5) (ref bv 2) ;=> 5 ``` @@ -96,7 +90,7 @@ types; SRFI-9 and R7RS cannot.) Some notes on specific types: ``` (define table (make-eqv-hashtable)) (ref table "foo" 'not-found) ;=> not-found - (set! table "foo" "Foobar.") + (set! (~ table "foo") "Foobar.") (ref table "foo") ;=> "Foobar." (ref table "bar") ;error: Object has no entry for field. ``` @@ -122,7 +116,7 @@ types; SRFI-9 and R7RS cannot.) Some notes on specific types: (b foo-b)) (define foo (make-foo 0 1)) (ref foo 'a) ;=> 0 - (set! foo 'b 2) ;error: No such assignable field of record. + (set! (~ foo 'b) 2) ;error: No such assignable field of record. ``` Alists are difficult to support due to the lack of a reliable `alist?` @@ -197,10 +191,6 @@ 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* ...)` @@ -209,29 +199,13 @@ 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))) +It has an associated SRFI-17 setter, which does the expected thing: -- `(set! object field value)` (syntax) + (set! (~ obj f1 f2 f3) value) -Sets the value for `field` in `object` to `value`. - -Valid types for `object` and `field` are the same as in the `ref` -procedure. Valid types for `value` are whatever values `object` may -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))`. 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. +changes the value that would be returned from `(~ obj f1 f2 f3)` to +`value`. Note that this procedure can be accessed as `(setter ref*)` +when needed: (define (store-item! field-chain value) (apply (setter ref*) the-store (append field-chain (list value)))) @@ -240,9 +214,8 @@ as `(setter ref*)` when needed. 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). +associated with it, and `sparse?` is a Boolean indicating whether the +type is a sparse type (see `ref` specification). Considerations when using as a library @@ -253,12 +226,12 @@ 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 -more typically, the ones from `(scheme base)` excluded. +The `define-record-type` export of the library conflicts with the one +in `(scheme base)`, so either has to be renamed, or more typically, +the one from `(scheme base)` excluded. Record types not defined with the `define-record-type` exported by -this library won't work with `ref` and `set!`. +this library won't work with `ref` and `ref*`. The `define-record-type` exported by this library expands to a record type definition followed with a command, essentially eliminating the @@ -286,7 +259,14 @@ small constant factor: one call to each type predicate. Acknowledgments --------------- -Original idea and some input during design by Jorgen Schäfer. +Thanks to Jorgen Schäfer for inspiring me to write this SRFI and +making the initial suggestion for the `ref` procedure and ternary +`set!` syntax. + +The `ref*` procedure with its `~` synonym and SRFI-17 setter (which +replaced the initially considered ternary `set!` syntax) seems to have +first appeared in Gauche. Thanks to Shiro Kawai: +<http://blog.practical-scheme.net/gauche/20100428-shorter-names> Copyright and license diff --git a/srfi/123.body.scm b/srfi/123.body.scm @@ -106,27 +106,22 @@ (%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 (%set! object field value) + (let ((setter (lookup-setter object))) + (setter object field value))) (define ref (getter-with-setter %ref (lambda (object field value) - (set! 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) + (%set! object field rest0) (apply set!* (ref object field) rest0 rest))))) (define ~ ref*) @@ -178,8 +173,8 @@ (define (register-getter-with-setter! type getter sparse?) (push! type-list type) - (set! getter-table type getter) - (set! setter-table type (setter getter)) + (set! (~ getter-table type) getter) + (set! (~ setter-table type) (setter getter)) (when sparse? (push! sparse-types type))) diff --git a/srfi/123.sld b/srfi/123.sld @@ -1,14 +1,17 @@ (define-library (srfi 123) (export - ref ref* ~ $bracket-apply$ set! define-record-type) + ref ref* ~ register-getter-with-setter! + $bracket-apply$ + define-record-type + set! setter getter-with-setter) (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!))) + (srfi 17) + (srfi 31)) (cond-expand ((library (srfi 4)) (import (srfi 4)))