Re: Functional record setters, a different approach

2012-11-10 Thread Ludovic Courtès
Hello!

Documentation attached.  Comments?

BTW, why does ‘set-field’ has the record as its 2nd argument instead of
1st (unlike ‘set-fields’)?

Thanks,
Ludo’.

From f7877d47009dc85e74bc63fd562b77f552a54bd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= l...@gnu.org
Date: Sat, 10 Nov 2012 17:27:14 +0100
Subject: [PATCH] doc: Document SRFI-9 functional setters.

* doc/ref/api-compound.texi (Functional ``Setters''): New section.
---
 doc/ref/api-compound.texi |  101 +
 1 file changed, 101 insertions(+)

diff --git a/doc/ref/api-compound.texi b/doc/ref/api-compound.texi
index b3fe0bd..0451368 100644
--- a/doc/ref/api-compound.texi
+++ b/doc/ref/api-compound.texi
@@ -2398,6 +2398,107 @@ This example prints the employee's name in brackets, for instance @code{[Fred]}.
 (write-char #\] port)))
 @end example
 
+@unnumberedsubsubsec Functional ``Setters''
+
+@cindex functional setters
+
+When writing code in a functional style, it is desirable to never alter
+the contents of records.  For such code, a simple way to return new
+record instances based on existing ones is highly desirable.
+
+The @code{(srfi srfi-9 gnu)} module extends SRFI-9 with facilities to
+return new record instances based on existing ones, only with one or
+more field values changed---@dfn{functional setters}.  First, the
+@code{define-immutable-record-type} works like
+@code{define-record-type}, except that setters are defined as functional
+setters.
+
+@deffn {Scheme Syntax} define-immutable-record-type type @* (constructor fieldname @dots{}) @* predicate @* (fieldname accessor [modifier]) @dots{}
+Define @var{type} as a new record type, like @code{define-record-type}.
+However, the record type is made @emph{immutable} (records may not be
+mutated, even with @code{struct-set!}), and any @var{modifier} is
+defined to be a functional setter---a procedure that returns a new
+record instance with the specified field changed, and leaves the
+original unchanged (see example below.)
+@end deffn
+
+@noindent
+In addition, the generic @code{set-field} and @code{set-fields} macros
+may be applied to any SRFI-9 record.
+
+@deffn {Scheme Syntax} set-field (field sub-fields ...) record value
+Return a new record of @var{record}'s type whose fields are equal to
+the corresponding fields of @var{record} except for the one specified by
+@var{field}.
+
+@var{field} must be the name of the getter corresponding to the field of
+@var{record} being ``set''.  Subsequent @var{sub-fields} must be record
+getters designating sub-fields within that field value to be set (see
+example below.)
+@end deffn
+
+@deffn {Scheme Syntax} set-fields record ((field sub-fields ...) value) ...
+Like @code{set-field}, but can be used to set more than one field at a
+time.  This expands to code that is more efficient than a series of
+single @code{set-field} calls.
+@end deffn
+
+To illustrate the use of functional setters, let's assume these two
+record type definitions:
+
+@example
+(define-record-type address
+  (address street city country)
+  address?
+  (street  address-street)
+  (cityaddress-city)
+  (country address-country))
+
+(define-immutable-record-type person
+  (person age email address)
+  person?
+  (age person-age set-person-age)
+  (email   person-email set-person-email)
+  (address person-address set-person-address))
+@end example
+
+@noindent
+First, note that the @code{person} record type definition introduces
+named functional setters.  These may be used like this:
+
+@example
+(define fsf-address
+  (address Franklin Street Boston USA))
+
+(define rms
+  (person 30 rms@@gnu.org fsf-address))
+
+(and (equal? (set-person-age rms 60)
+ (person 60 rms@@gnu.org fsf-address))
+ (= (person-age rms) 30))
+@result{} #t
+@end example
+
+@noindent
+Here, the original @code{person} record, to which @var{rms} is bound,
+is left unchanged.
+
+Now, suppose we want to change both the street and age of @var{rms}.
+This can be achieved using @code{set-fields}:
+
+@example
+(set-fields rms
+  ((person-age) 60)
+  ((person-address address-street) Temple Place))
+@result{} #person age: 60 email: rms@@gnu.org
+  address: #address street: Temple Place city: Boston country: USA
+@end example
+
+@noindent
+Notice how the above changed two fields of @var{rms}, including the
+@code{street} field of its @code{address} field, in a concise way.  Also
+note that @code{set-fields} works equally well for types defined with
+just @code{define-record-type}.
 
 @node Records
 @subsection Records
-- 
1.7.10.4



Re: Functional record setters, a different approach

2012-11-10 Thread Mark H Weaver
Hi Ludovic,

l...@gnu.org (Ludovic Courtès) writes:
 Documentation attached.  Comments?

Thanks!  Looks good to me, modulo a few comments below.

 BTW, why does ‘set-field’ has the record as its 2nd argument instead of
 1st (unlike ‘set-fields’)?

Good question.  I followed the syntax of 'set-field' from your original
patch, but that argument order did not make sense for 'set-fields'.

On one hand, (set-field (person-address address-city) person Boston)
matches the order of the corresponding english set the field
(person-address address-city) of person to Boston, and thus reads a bit
more naturally to my ears.

On the other hand, it would be good for the two syntaxes to be
consistent with each other, and (set-field record field value)
would also be more consistent with things like 'hash-set!',
'vector-set!', etc.

Honestly, I could go either way.  If you think it makes sense to change
the order to (set-field record field value), I'd be glad to make
that change.  Obviously it's now or never :)

 +@unnumberedsubsubsec Functional ``Setters''
 +
 +@cindex functional setters
 +
 +When writing code in a functional style, it is desirable to never alter
 +the contents of records.  For such code, a simple way to return new
 +record instances based on existing ones is highly desirable.
 +
 +The @code{(srfi srfi-9 gnu)} module extends SRFI-9 with facilities to
 +return new record instances based on existing ones, only with one or
 +more field values changed---@dfn{functional setters}.  First, the
 +@code{define-immutable-record-type} works like
 +@code{define-record-type}, except that setters are defined as functional
 +setters.

except that the fields are immutable and the setters are ...

[...]

 +@deffn {Scheme Syntax} set-field (field sub-fields ...) record value
 +Return a new record of @var{record}'s type whose fields are equal to
 +the corresponding fields of @var{record} except for the one specified by
 +@var{field}.
 +
 +@var{field} must be the name of the getter corresponding to the field of
 +@var{record} being ``set''.  Subsequent @var{sub-fields} must be record

This is the first time that getter is used, but it has not been made
clear that you mean what has been called an accessor elsewhere in the
doc.  More generally, there is a confusing mixture of the
accessor/modifier and getter/setter terminology.  I wonder if it would
made sense to do some kind of find/replace in this section.

Other than that, it looks great, thanks!

 Mark



Re: Functional record setters, a different approach

2012-11-10 Thread Ludovic Courtès
Mark H Weaver m...@netris.org skribis:

 l...@gnu.org (Ludovic Courtès) writes:

[...]

 BTW, why does ‘set-field’ has the record as its 2nd argument instead of
 1st (unlike ‘set-fields’)?

 Good question.  I followed the syntax of 'set-field' from your original
 patch, but that argument order did not make sense for 'set-fields'.

In the meantime we concurred on IRC that keeping the record as the first
argument in both cases may be best.

 +The @code{(srfi srfi-9 gnu)} module extends SRFI-9 with facilities to
 +return new record instances based on existing ones, only with one or
 +more field values changed---@dfn{functional setters}.  First, the
 +@code{define-immutable-record-type} works like
 +@code{define-record-type}, except that setters are defined as functional
 +setters.

 except that the fields are immutable and the setters are ...

OK.

 +@deffn {Scheme Syntax} set-field (field sub-fields ...) record value
 +Return a new record of @var{record}'s type whose fields are equal to
 +the corresponding fields of @var{record} except for the one specified by
 +@var{field}.
 +
 +@var{field} must be the name of the getter corresponding to the field of
 +@var{record} being ``set''.  Subsequent @var{sub-fields} must be record

 This is the first time that getter is used, but it has not been made
 clear that you mean what has been called an accessor elsewhere in the
 doc.  More generally, there is a confusing mixture of the
 accessor/modifier and getter/setter terminology.  I wonder if it would
 made sense to do some kind of find/replace in this section.

Yeah.  I ended up leaving “getter”, because that’s the term used in the
SRFI-9 node from the beginning.  But I agree we might need to do some
find/replace at some point.

Ludo’.



Re: Functional record setters, a different approach

2012-11-09 Thread Mark H Weaver
Hi Ludovic,

l...@gnu.org (Ludovic Courtès) writes:
 Mark H Weaver m...@netris.org skribis:
 +(pass-if set-fields with one path as a prefix of another
 +  (let ()
 +(define-immutable-record-type foo (make-foo x) foo?
 +  (x foo-x)
 +  (y foo-y set-foo-y)
 +  (z foo-z set-foo-z))
 +
 +(define-immutable-record-type :bar (make-bar i j) bar?
 +  (i bar-i)
 +  (j bar-j set-bar-j))
 +
 +(catch 'syntax-error
 + (lambda ()
 +   (compile '(let ((s (make-bar (make-foo 5) 2)))
 +   (set-fields s
 + ((bar-i foo-x) 1)
 + ((bar-i foo-z) 2)
 + ((bar-i) 3)))
 +#:env (current-module))
 +   #f)
 + (lambda (key whom what src form subform)
 +   (equal? (list key whom what form subform)
 +   '(syntax-error set-fields
 +  one field path is a prefix of another
 +  (set-fields s
 +((bar-i foo-x) 1)
 +((bar-i foo-z) 2)
 +((bar-i) 3))
 +  (bar-i)

 You might want to use ‘pass-if-equal’ here, for better reporting.

I see now what you meant, and I just pushed a patch to convert these to
use 'pass-if-equal'.

I also pushed a couple more patches to improve error reporting.

Thanks!
  Mark



Re: Functional record setters, a different approach

2012-11-08 Thread Ludovic Courtès
Hi Mark!

Mark H Weaver m...@netris.org skribis:

 I've attached a slightly improved functional record setters patch.
 The only change since yesterday's version is to the test suite, which
 now includes tests of the compile-time error checking.

Nice, thanks!

It addresses my main concern with the previous version of the patch,
which is that it lacked support for named setters, so that’s great.

At the time you were concerned about the “weight” of these macros.  Are
you just more relaxed now, or do you have a psyntax optimization in the
pipeline?  :-)

 +  (make-address Foo Paris France
 +  (and (equal? (set-fields p
 + ((person-email) b...@example.com)
 + ((person-address address-country) Spain)
 + ((person-address address-city) Barcelona))

The choice of towns seems inaccurate.  ;-)

 +(pass-if set-fields with one path as a prefix of another
 +  (let ()
 +(define-immutable-record-type foo (make-foo x) foo?
 +  (x foo-x)
 +  (y foo-y set-foo-y)
 +  (z foo-z set-foo-z))
 +
 +(define-immutable-record-type :bar (make-bar i j) bar?
 +  (i bar-i)
 +  (j bar-j set-bar-j))
 +
 +(catch 'syntax-error
 + (lambda ()
 +   (compile '(let ((s (make-bar (make-foo 5) 2)))
 +   (set-fields s
 + ((bar-i foo-x) 1)
 + ((bar-i foo-z) 2)
 + ((bar-i) 3)))
 +#:env (current-module))
 +   #f)
 + (lambda (key whom what src form subform)
 +   (equal? (list key whom what form subform)
 +   '(syntax-error set-fields
 +  one field path is a prefix of another
 +  (set-fields s
 +((bar-i foo-x) 1)
 +((bar-i foo-z) 2)
 +((bar-i) 3))
 +  (bar-i)

You might want to use ‘pass-if-equal’ here, for better reporting.

Please commit, I’ll take care of the doc.

Thanks!

Ludo’.




Re: Functional record setters, a different approach

2012-11-08 Thread Mark H Weaver
Hi Ludovic!

l...@gnu.org (Ludovic Courtès) writes:
 At the time you were concerned about the “weight” of these macros.  Are
 you just more relaxed now, or do you have a psyntax optimization in the
 pipeline?  :-)

I did some experiments, and on my 64-bit system this patch increases the
bytecode associated with a record type definition by about 2755 bytes
plus 852 bytes per field.  I can live with that.

 +  (and (equal? (set-fields p
 + ((person-email) b...@example.com)
 + ((person-address address-country) Spain)
 + ((person-address address-city) Barcelona))

 The choice of towns seems inaccurate.  ;-)

Heh, good point.  I changed Spain to Catalonia :)

 +(pass-if set-fields with one path as a prefix of another
 +  (let ()
 +(define-immutable-record-type foo (make-foo x) foo?
 +  (x foo-x)
 +  (y foo-y set-foo-y)
 +  (z foo-z set-foo-z))
 +
 +(define-immutable-record-type :bar (make-bar i j) bar?
 +  (i bar-i)
 +  (j bar-j set-bar-j))
 +
 +(catch 'syntax-error
 + (lambda ()
 +   (compile '(let ((s (make-bar (make-foo 5) 2)))
 +   (set-fields s
 + ((bar-i foo-x) 1)
 + ((bar-i foo-z) 2)
 + ((bar-i) 3)))
 +#:env (current-module))
 +   #f)
 + (lambda (key whom what src form subform)
 +   (equal? (list key whom what form subform)
 +   '(syntax-error set-fields
 +  one field path is a prefix of another
 +  (set-fields s
 +((bar-i foo-x) 1)
 +((bar-i foo-z) 2)
 +((bar-i) 3))
 +  (bar-i)

 You might want to use ‘pass-if-equal’ here, for better reporting.

'pass-if-equal' seems inapplicable here, since I'm testing for an
exception.  'pass-if-exception' was closer to what I needed, but I
wanted to verify more than just the exception key.

 Please commit, I’ll take care of the doc.

Done, and thanks :)

 Mark



Re: Functional record setters, a different approach

2012-11-07 Thread Mark H Weaver
Hello all,

Apologies for the long delay on this, but I've finally produced an
improved implementation of functional record setters for stable-2.0.

It is fully compatible with the earlier API proposed by Ludovic in
http://lists.gnu.org/archive/html/guile-devel/2012-04/msg00032.html
(i.e. 'define-immutable-record-type' and 'set-field'), but also adds
'set-fields' for efficiently setting multiple fields at once (to
arbitrary depth).  I also spent a lot of effort to produce good
compile-time error messages where possible.

See srfi-9.test for example usage.

The only thing missing is the documentation, which Ludovic kindly
offered to write.

Comments and suggestions solicited.

 Mark


From cb09314846faf62461d63b17e108a95d7cff18c4 Mon Sep 17 00:00:00 2001
From: Mark H Weaver m...@netris.org
Date: Wed, 7 Nov 2012 12:21:44 -0500
Subject: [PATCH] Implement functional record setters.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Written in collaboration with Ludovic Courtès l...@gnu.org

* module/srfi/srfi-9.scm: Internally, rename 'accessor' to 'getter'
  and 'modifier' to 'setter'.

  (define-tagged-inlinable, getter-type, getter-index, getter-copier,
  %%on-error, %%set-fields): New macros.

  (%define-record-type): New macro for creating both mutable and
  immutable records, and containing a substantially rewritten version of
  the code formerly in 'define-record-type'.

  (define-record-type): Now just a wrapper for '%define-record-type'.

  (throw-bad-struct, make-copier-id): New procedures.

* module/srfi/srfi-9/gnu.scm (define-immutable-record-type, set-field,
  and set-fields): New exported macros.

  (collate-set-field-specs): New procedure.

  (%set-fields-unknown-getter, %set-fields): New macros.

* test-suite/tests/srfi-9.test: Add tests.
---
 module/srfi/srfi-9.scm   |  252 --
 module/srfi/srfi-9/gnu.scm   |  100 +++-
 test-suite/tests/srfi-9.test |  348 ++
 3 files changed, 589 insertions(+), 111 deletions(-)

diff --git a/module/srfi/srfi-9.scm b/module/srfi/srfi-9.scm
index da71d1e..1dd132a 100644
--- a/module/srfi/srfi-9.scm
+++ b/module/srfi/srfi-9.scm
@@ -29,8 +29,8 @@
 ;; predicate name
 ;; field spec ...)
 ;;
-;;  field spec - (field tag accessor name)
-;;   - (field tag accessor name modifier name)
+;;  field spec - (field tag getter name)
+;;   - (field tag getter name setter name)
 ;;
 ;;  field tag - identifier
 ;;  ... name  - identifier
@@ -68,8 +68,31 @@
 ;; because the public one has a different `make-procedure-name', so
 ;; using it would require users to recompile code that uses SRFI-9.  See
 ;; http://lists.gnu.org/archive/html/guile-devel/2011-04/msg00111.html.
+;;
+
+(define-syntax-rule (define-inlinable (name formals ...) body ...)
+  (define-tagged-inlinable () (name formals ...) body ...))
+
+;; 'define-tagged-inlinable' has an additional feature: it stores a map
+;; of keys to values that can be retrieved at expansion time.  This is
+;; currently used to retrieve the rtd id, field index, and record copier
+;; macro for an arbitrary getter.
+
+(define-syntax-rule (%%on-error err) err)
+
+(define %%type #f)   ; a private syntax literal
+(define-syntax-rule (getter-type getter err)
+  (getter (%%on-error err) %%type))
 
-(define-syntax define-inlinable
+(define %%index #f)  ; a private syntax literal
+(define-syntax-rule (getter-index getter err)
+  (getter (%%on-error err) %%index))
+
+(define %%copier #f) ; a private syntax literal
+(define-syntax-rule (getter-copier getter err)
+  (getter (%%on-error err) %%copier))
+
+(define-syntax define-tagged-inlinable
   (lambda (x)
 (define (make-procedure-name name)
   (datum-syntax name
@@ -77,7 +100,7 @@
 '-procedure)))
 
 (syntax-case x ()
-  ((_ (name formals ...) body ...)
+  ((_ ((key value) ...) (name formals ...) body ...)
(identifier? #'name)
(with-syntax ((proc-name  (make-procedure-name #'name))
  ((args ...) (generate-temporaries #'(formals ...
@@ -86,7 +109,8 @@
body ...)
  (define-syntax name
(lambda (x)
- (syntax-case x ()
+ (syntax-case x (%%on-error key ...)
+   ((_ (%%on-error err) key) #'value) ...
((_ args ...)
 #'((lambda (formals ...)
  body ...)
@@ -109,90 +133,149 @@
   (loop (cdr fields) (+ 1 off)
   (display  p))
 
-(define-syntax define-record-type
+(define (throw-bad-struct s who)
+  (throw 'wrong-type-arg who
+ Wrong type argument: ~S (list s)
+ (list s)))
+
+(define (make-copier-id type-name)
+  (datum-syntax type-name
+ (symbol-append '%% (syntax-datum type-name)
+'-set-fields)))
+
+(define-syntax %%set-fields
+  

Re: Functional record setters, a different approach

2012-11-07 Thread Mark H Weaver
Hello all,

I've attached a slightly improved functional record setters patch.
The only change since yesterday's version is to the test suite, which
now includes tests of the compile-time error checking.

Here's a brief overview of the provided functionality.

First, 'define-immutable-record-type' is very similar to SRFI-9's
'define-record-type', but the (optional) third element of each field
spec is a purely functional record setter.  Unlike the usual destructive
setters which mutate a record in place, a functional record setter
returns a freshly allocated record that's the same as the existing one
but with one field changed, e.g.:

(use-modules (srfi srfi-9)
 (srfi srfi-9 gnu))

(define-immutable-record-type address
  (make-address street city)
  address?
  (street address-street set-address-street)
  (city   address-city   set-address-city))

(define addr (make-address Foo Paris))
addr
= #address street: Foo city: Paris

(set-address-street addr Bar)
= #address street: Bar city: Paris

addr
= #address street: Foo city: Paris

'set-field' allows you to non-destructively set a field at an
arbitrary depth within a nested structure, e.g.:

(define-immutable-record-type person
  (make-person age email address)
  person?
  (age person-age)
  (email   person-email)
  (address person-address))

(define p (make-person 30 f...@example.com
   (make-address Foo Paris)))
p
= #person age: 30 email: f...@example.com
address: #address street: Foo city: Paris

(set-field (person-address address-city) p Düsseldorf)
= #person age: 30 email: f...@example.com
address: #address street: Foo city: Düsseldorf

p
= #person age: 30 email: f...@example.com
address: #address street: Foo city: Paris

'set-fields' allows you to non-destructively set any number of fields
(of arbitrary depth), and accomplishes this with the minimal number of
allocations, sharing as much as possible with the original structure.

(set-fields p
  ((person-email) b...@example.com)
  ((person-address address-city) Düsseldorf))
= #person age: 30 email: b...@example.com
address: #address street: Foo city: Düsseldorf

(define p2 (set-fields p
 ((person-age) 20)
 ((person-email) foo...@example.com)))
p2
= #person age: 20 email: foo...@example.com
address: #address street: Foo city: Paris

(eq? (person-address p) (person-address p2))
= #t

Note that 'set-field' and 'set-fields' can also be used with traditional
mutable SRFI-9 records, or any mixture of mutable and immutable records.

Comments and suggestions solicited.

  Mark


From 274c795382308f537aea620c3972cff291624cce Mon Sep 17 00:00:00 2001
From: Mark H Weaver m...@netris.org
Date: Wed, 7 Nov 2012 12:21:44 -0500
Subject: [PATCH] Implement functional record setters.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Written in collaboration with Ludovic Courtès l...@gnu.org

* module/srfi/srfi-9.scm: Internally, rename 'accessor' to 'getter'
  and 'modifier' to 'setter'.

  (define-tagged-inlinable, getter-type, getter-index, getter-copier,
  %%on-error, %%set-fields): New macros.

  (%define-record-type): New macro for creating both mutable and
  immutable records, and containing a substantially rewritten version of
  the code formerly in 'define-record-type'.

  (define-record-type): Now just a wrapper for '%define-record-type'.

  (throw-bad-struct, make-copier-id): New procedures.

* module/srfi/srfi-9/gnu.scm (define-immutable-record-type, set-field,
  and set-fields): New exported macros.

  (collate-set-field-specs): New procedure.

  (%set-fields-unknown-getter, %set-fields): New macros.

* test-suite/tests/srfi-9.test: Add tests.  Rename getters and setters
  in existing tests to make the functional setters look better.
---
 module/srfi/srfi-9.scm   |  252 ---
 module/srfi/srfi-9/gnu.scm   |  100 +++-
 test-suite/tests/srfi-9.test |  544 +++---
 3 files changed, 785 insertions(+), 111 deletions(-)

diff --git a/module/srfi/srfi-9.scm b/module/srfi/srfi-9.scm
index da71d1e..1dd132a 100644
--- a/module/srfi/srfi-9.scm
+++ b/module/srfi/srfi-9.scm
@@ -29,8 +29,8 @@
 ;; predicate name
 ;; field spec ...)
 ;;
-;;  field spec - (field tag accessor name)
-;;   - (field tag accessor name modifier name)
+;;  field spec - (field tag getter name)
+;;   - (field tag getter name setter name)
 ;;
 ;;  field tag - identifier
 ;;  ... name  - identifier
@@ -68,8 +68,31 @@
 ;; because the public one has a different `make-procedure-name', so
 ;; using it would require users to recompile code that uses SRFI-9.  See
 ;; 

Re: Functional record setters, a different approach

2012-05-15 Thread Ludovic Courtès
Hi Mark,

Mark H Weaver m...@netris.org skribis:

 l...@gnu.org (Ludovic Courtès) writes:
 Mark H Weaver m...@netris.org skribis:
 l...@gnu.org (Ludovic Courtès) writes:

 I’ll let you see whether/how you can borrow from this in your code, if
 that’s fine with you.

 Okay, will do.

 Any progress on this?  ;-)

 I apologize for dropping on the ball on this.  The primary concern I
 have with my current approach is that 'define-tagged-inlinable' adds
 several more huge bloated syntax-objects to the getter macros.  Of
 course, as usual, this bloat will never be used, but everyone must pay
 for 'datum-syntax' whether they use it or not :-(

Are you concerned about the size of ‘srfi-9.go’, or is there some other
issue at stake?

While I agree that bloating .go files is not satisfying, I would not
consider it a showstopper here.

I suppose the only way to use fewer syntax objects would be to use a
specialize variant of ‘define-tagged-inlinable’, no?  That would make
the code less elegant.

 I’m happy to help with updating the docs, for instance, if you want.

 That would be very helpful, thanks!

OK, I’ll do that when the code is checked in.

Thanks,
Ludo’.



Re: Functional record setters, a different approach

2012-05-07 Thread Ludovic Courtès
Hi Mark!

Mark H Weaver m...@netris.org skribis:

 l...@gnu.org (Ludovic Courtès) writes:

 I’ll let you see whether/how you can borrow from this in your code, if
 that’s fine with you.

 Okay, will do.

Any progress on this?  ;-)

I’m happy to help with updating the docs, for instance, if you want.

Thanks,
Ludo’.




Re: Functional record setters, a different approach

2012-04-13 Thread Ludovic Courtès
Hi Mark!

And Happy Friday, as our friend would say.  :-)

Mark H Weaver m...@netris.org skribis:

 l...@gnu.org (Ludovic Courtès) writes:
 Mark H Weaver m...@netris.org skribis:
 l...@gnu.org (Ludovic Courtès) writes:
 I’d still want named single-field setters, for convenience.  For that,
 we probably still need a separate ‘define-immutable-record-type’.

 Agreed.

 Cool!  Could you integrate it somehow, along with the tests I provided?

 Will do.

Thanks!

 However, I find the term 'set' misleading, since no mutation is taking
 place.  Maybe 'update'?  I dunno, I don't have strong feelings on this.

 I don’t find ‘set’ misleading because there’s no exclamation mark, and
 because it’s conceptually about setting a field’s value.  WDYT?

 Okay, on second thought I'm inclined to agree.  'set' is a good choice.

[...]

 What do you think?

I’d say ‘set-fields’, no?

(There are actually two common conventions: one is TYPE-METHOD, as in
‘vector-ref’, and another is VERB-NAME, as is ‘make-list’, ‘let-values’.
I tend to prefer the latter, but sometimes the former is more convenient
or clearer.)

[...]

 Would these checks be alleviated by Andy’s work on peval “predicates”?

 Unfortunately, no.  The 'vtable' field of a struct is a mutable field,
 and in fact when a GOOPS class is redefined, the 'vtable' field of
 instances are modified.  This means that it is not safe for the compiler
 to eliminate redundant calls to 'struct-vtable'.

Oh, OK.  That is evl.

 For example, for (modified-copy s ((foo-x) 'new)) where 's' contains 10
 fields, the expanded code would include 9 separate checks that 's' is
 the right type.

 Couldn’t ‘modified-copy’ be implemented differently so that there’s only
 one check?  That seems like the most obvious (not necessarily the
 easiest) way to address the problem.

 Yes, and that's exactly what I did.  However, I was only able to
 accomplish this by essentially hacking up my own 'getter-nocheck'.

 If I had used the normal getters in the expansion of 'modified-copy',
 then (modified-copy s ((foo-x) 'new)) would expand to:

   (make-struct foo 0
 (foo-q s) (foo-r s) (foo-s s) (foo-t s) (foo-u s)
 (foo-v s) (foo-w s) 'new  (foo-y s) (foo-z s))

 and each of those getter uses would include a type-check in their
 expansions.  As you suggested, I instead wrap a single check around the
 whole thing and then effectively use (foo-*-nocheck s) instead, by using
 'struct-ref' directly.

 This example is intended to convince you that 'nocheck' variants of
 struct accessors are important as a base upon which to build efficient
 higher-level constructs, at least for our own internal use.

I view it as an important “implementation detail”, but not as an API to
be exposed publicly.

What about keeping it private until we find an actual use case where it
is required outside of (srfi srfi-9)?

Thanks!

Ludo’.



Re: Functional record setters, a different approach

2012-04-13 Thread Mark H Weaver
Hi Ludovic!

l...@gnu.org (Ludovic Courtès) writes:
 I’d say ‘set-fields’, no?

Okay, good enough.

 Would these checks be alleviated by Andy’s work on peval “predicates”?

 Unfortunately, no.  The 'vtable' field of a struct is a mutable field,
 and in fact when a GOOPS class is redefined, the 'vtable' field of
 instances are modified.  This means that it is not safe for the compiler
 to eliminate redundant calls to 'struct-vtable'.

 Oh, OK.  That is evl.

It turns out that I had some misconceptions about this.  Although it is
true that the first word of a struct cell is mutated, that's actually
not what 'struct-vtable' returns nowadays.  Class redefinition involves
a rather complicated dance described here:

  http://wingolog.org/archives/2009/11/09/class-redefinition-in-guile

So, the result of 'struct-vtable' does not change after all, at least
not for plain instances.  (It's not yet clear to me whether the vtable
of a redefined class object itself can be changed).

Regardless, 'struct-vtable' checks usually involve comparison with the
value of a mutable top-level variable, and of course the compiler must
assume that mutable variables might change whenever unknown code is run
(e.g. when any top-level procedure is called).

 This example is intended to convince you that 'nocheck' variants of
 struct accessors are important as a base upon which to build efficient
 higher-level constructs, at least for our own internal use.

 I view it as an important “implementation detail”, but not as an API to
 be exposed publicly.

 What about keeping it private until we find an actual use case where it
 is required outside of (srfi srfi-9)?

Okay, fair enough :)

   Thanks,
 Mark



Re: Functional record setters, a different approach

2012-04-12 Thread Mark H Weaver
Hi Ludovic!

l...@gnu.org (Ludovic Courtès) writes:
 Mark H Weaver m...@netris.org skribis:
 The public interface I've created is quite a bit different than what
 we've been discussing so far.  I'm open to changing it, but here's what
 the attached patch currently exports from (srfi srfi-9 gnu):

   (modified-copy struct-expr (field-path expr) ...)
   (modified-copy-nocheck struct-expr (field-path expr) ...)

 where field-path is of the form (field ...)

 I’d still want named single-field setters, for convenience.  For that,
 we probably still need a separate ‘define-immutable-record-type’.

Agreed.

 Also, I’d want to avoid the term ‘copy’, which is sounds low-level;
 ‘set’ seems more appropriate to me.  WDYT?

I agree that 'copy' is not a good term to use here, especially since no
copy is made in the zero-modification case of (modified-copy s).

However, I find the term 'set' misleading, since no mutation is taking
place.  Maybe 'update'?  I dunno, I don't have strong feelings on this.

 Regarding the interface for multi-field nested changes, I’d still
 prefer:

   (set-field p (foo bar) val
(foo baz) chbouib)

 Or is it less readable than:

   (set-field p ((foo bar) val)
((foo baz) chbouib))

I find the first variant to be very un-scheme-like.  One could make the
same argument about 'let', 'let-values', or 'cond', but the Scheme
community has consistently chosen the latter style of syntax.  The
latter style allows better extensibility.  Also, the 'syntax-rules'
pattern matching machinery works very nicely with the usual scheme
syntax, and poorly for your proposed syntax.  I feel fairly strongly
about this.

 Finally, I think there’s shouldn’t be a ‘-nocheck’ version.  Dynamic
 typing entails run-time type checking, that’s a fact of life, but safety
 shouldn’t have to be traded for performance.

Hmm.  I agree that the 'nocheck' variant should not be prominently
mentioned, and perhaps not documented at all, but I suspect it will
prove useful to keep it around, even if only for our own internal use to
build efficient higher-level constructs.

For example, when I built this 'modified-copy' machinery, I was unable
to build upon the usual (getter s) syntax, because that would cause
the generated code to include many redundant checks (one for each field
retrieved).

For example, for (modified-copy s ((foo-x) 'new)) where 's' contains 10
fields, the expanded code would include 9 separate checks that 's' is
the right type.  Even when our compiler becomes smart enough to
eliminate those redundant checks, it creates a lot of extra work for the
compiler, slowing everything down, and of course when interpreting (or
compiling without optimization) it is a serious lose.

Therefore, I needed a 'nocheck' version of the individual getters, so
that I could check the type just once and then fetch each individual
field without additional checks.  Unfortunately, there was none, so I
needed I hack around this limitation, adding a mechanism to retrieve the
field-index from a getter at expansion time, and then using 'struct-ref'
directly.  This is ugly.  I'd have preferred to have a 'nocheck' getter
instead.

In summary, my view is that in order to enable practical elegant
programming for users, we sometimes need to do less elegant (or even
downright ugly) things in the lower levels.  Otherwise the system will
be too slow, and users will reject elegant constructs such as functional
setters and just use plain mutation instead.

 These macros can be used on _any_ srfi-9 record, not just ones specially
 declared as immutable.

 I assume this preserves “ABI” compatibility too, right?

Yes.

 However, in the future, could you please reply in the same thread,

You're right, I should have done so.

 and more importantly coordinate so we don’t waste time working on the
 same code in parallel.

I started this work after you were (probably) asleep, and rushed to post
about it before you woke up, so I did my best there.  If you would
prefer to use your own code instead, that's okay with me.  As long as we
end up with a functional-multi-setter that generates good code, I'll be
satisfied.

 FWIW I was using this approach to represent the tree of accessors:
 
 (define (field-tree fields)
   ;; Given FIELDS, a list of field-accessor-lists, return a tree
   ;; that groups together FIELDS by prefix.  Example:
   ;;   FIELDS:  ((f1 f2 f3) (f1 f4))
   ;;   RESULT:  ((f1 (f2 (f3)) (f4)))
   (define (insert obj tree)
 (match obj
   ((head tail ...)
(let ((sub (or (assoc-ref tree head) '(
  (cons (cons head (insert tail sub))
(alist-delete head tree
   (()
tree)))
 
   (fold-right insert '() fields))

I agree that this is much nicer than my corresponding code.  Thanks for
sharing.  Would you like me to incorporate something like this into my
code, or would you like to start with your code and maybe 

Re: Functional record setters, a different approach

2012-04-12 Thread Thien-Thi Nguyen
() Mark H Weaver m...@netris.org
() Thu, 12 Apr 2012 11:04:13 -0400

   However, I find the term 'set' misleading, since no mutation is
   taking place.  Maybe 'update'?  I dunno, I don't have strong
   feelings on this.

How about ‘overlay’ (or ‘over-set’ or ‘mask’) or ‘interpose’ or
‘insinuate’ or ‘twiddle’ or ‘frob’ or ‘actually’ or ‘imho’ or some
combination of the previous and prefix ‘w/’ (e.g., ‘w/ho’)?



Re: Functional record setters, a different approach

2012-04-12 Thread Ludovic Courtès
Hi Mark!

Mark H Weaver m...@netris.org skribis:

 l...@gnu.org (Ludovic Courtès) writes:
 Mark H Weaver m...@netris.org skribis:
 The public interface I've created is quite a bit different than what
 we've been discussing so far.  I'm open to changing it, but here's what
 the attached patch currently exports from (srfi srfi-9 gnu):

   (modified-copy struct-expr (field-path expr) ...)
   (modified-copy-nocheck struct-expr (field-path expr) ...)

 where field-path is of the form (field ...)

 I’d still want named single-field setters, for convenience.  For that,
 we probably still need a separate ‘define-immutable-record-type’.

 Agreed.

Cool!  Could you integrate it somehow, along with the tests I provided?

 Also, I’d want to avoid the term ‘copy’, which is sounds low-level;
 ‘set’ seems more appropriate to me.  WDYT?

 I agree that 'copy' is not a good term to use here, especially since no
 copy is made in the zero-modification case of (modified-copy s).

 However, I find the term 'set' misleading, since no mutation is taking
 place.  Maybe 'update'?  I dunno, I don't have strong feelings on this.

I don’t find ‘set’ misleading because there’s no exclamation mark, and
because it’s conceptually about setting a field’s value.  WDYT?

 Regarding the interface for multi-field nested changes, I’d still
 prefer:

   (set-field p (foo bar) val
(foo baz) chbouib)

 Or is it less readable than:

   (set-field p ((foo bar) val)
((foo baz) chbouib))

 I find the first variant to be very un-scheme-like.

Yes; in hindsight, I have the same feeling.

 Finally, I think there’s shouldn’t be a ‘-nocheck’ version.  Dynamic
 typing entails run-time type checking, that’s a fact of life, but safety
 shouldn’t have to be traded for performance.

 Hmm.  I agree that the 'nocheck' variant should not be prominently
 mentioned, and perhaps not documented at all, but I suspect it will
 prove useful to keep it around, even if only for our own internal use to
 build efficient higher-level constructs.

 For example, when I built this 'modified-copy' machinery, I was unable
 to build upon the usual (getter s) syntax, because that would cause
 the generated code to include many redundant checks (one for each field
 retrieved).

Would these checks be alleviated by Andy’s work on peval “predicates”?

 For example, for (modified-copy s ((foo-x) 'new)) where 's' contains 10
 fields, the expanded code would include 9 separate checks that 's' is
 the right type.

Couldn’t ‘modified-copy’ be implemented differently so that there’s only
one check?  That seems like the most obvious (not necessarily the
easiest) way to address the problem.

Every time ‘car’ is used, there’s a type-check that users cannot
eliminate.  IMO, if it were to be eliminated, it should be via
orthogonal means, not via the API: for instance, when the compiler knows
the check would always pass at run-time, or with (declare (unsafe)), or
with ‘-DSCM_RECKLESS’.  A ‘car-nocheck’ wouldn’t be convenient, would it?
:-)

[...]

 and more importantly coordinate so we don’t waste time working on the
 same code in parallel.

 I started this work after you were (probably) asleep,

When I don’t sleep, I also have a daytime work, and a family, among
other things, which is why I am not this responsive.  Also, I was
assuming I was holding a mutex on this, so-to-speak.  Lastly, I check
email less frequently when I have a hack on my mind.  :-)

 and rushed to post about it before you woke up, so I did my best
 there.  If you would prefer to use your own code instead, that's okay
 with me.  As long as we end up with a functional-multi-setter that
 generates good code, I'll be satisfied.

Yeah, me too.  So I’m happy we’re getting close to an even better
solution!

 FWIW I was using this approach to represent the tree of accessors:
 
 (define (field-tree fields)
   ;; Given FIELDS, a list of field-accessor-lists, return a tree
   ;; that groups together FIELDS by prefix.  Example:
   ;;   FIELDS:  ((f1 f2 f3) (f1 f4))
   ;;   RESULT:  ((f1 (f2 (f3)) (f4)))
   (define (insert obj tree)
 (match obj
   ((head tail ...)
(let ((sub (or (assoc-ref tree head) '(
  (cons (cons head (insert tail sub))
(alist-delete head tree
   (()
tree)))
 
   (fold-right insert '() fields))

 I agree that this is much nicer than my corresponding code.  Thanks for
 sharing.  Would you like me to incorporate something like this into my
 code, or would you like to start with your code and maybe cherry-pick
 ideas/code from mine?  Either way is fine with me.

I’ll let you see whether/how you can borrow from this in your code, if
that’s fine with you.

Thanks,
Ludo’.



Re: Functional record setters, a different approach

2012-04-12 Thread Mark H Weaver
Hi Ludovic!

l...@gnu.org (Ludovic Courtès) writes:
 Mark H Weaver m...@netris.org skribis:
 l...@gnu.org (Ludovic Courtès) writes:
 I’d still want named single-field setters, for convenience.  For that,
 we probably still need a separate ‘define-immutable-record-type’.

 Agreed.

 Cool!  Could you integrate it somehow, along with the tests I provided?

Will do.

 However, I find the term 'set' misleading, since no mutation is taking
 place.  Maybe 'update'?  I dunno, I don't have strong feelings on this.

 I don’t find ‘set’ misleading because there’s no exclamation mark, and
 because it’s conceptually about setting a field’s value.  WDYT?

Okay, on second thought I'm inclined to agree.  'set' is a good choice.

However, there's a problem with the name 'set-field': because 'field' is
a noun here, it should be made plural when more than one field is being
set.  We could avoid this grammatical problem by making 'field' an
adjective, as in 'field-set'.  This would also be consistent with the
names 'vector-set!', 'struct-set!', 'bitvector-set!', etc.

Another option is 'record-set'.

What do you think?

 Finally, I think there’s shouldn’t be a ‘-nocheck’ version.  Dynamic
 typing entails run-time type checking, that’s a fact of life, but safety
 shouldn’t have to be traded for performance.

 Hmm.  I agree that the 'nocheck' variant should not be prominently
 mentioned, and perhaps not documented at all, but I suspect it will
 prove useful to keep it around, even if only for our own internal use to
 build efficient higher-level constructs.

 For example, when I built this 'modified-copy' machinery, I was unable
 to build upon the usual (getter s) syntax, because that would cause
 the generated code to include many redundant checks (one for each field
 retrieved).

 Would these checks be alleviated by Andy’s work on peval “predicates”?

Unfortunately, no.  The 'vtable' field of a struct is a mutable field,
and in fact when a GOOPS class is redefined, the 'vtable' field of
instances are modified.  This means that it is not safe for the compiler
to eliminate redundant calls to 'struct-vtable'.

 For example, for (modified-copy s ((foo-x) 'new)) where 's' contains 10
 fields, the expanded code would include 9 separate checks that 's' is
 the right type.

 Couldn’t ‘modified-copy’ be implemented differently so that there’s only
 one check?  That seems like the most obvious (not necessarily the
 easiest) way to address the problem.

Yes, and that's exactly what I did.  However, I was only able to
accomplish this by essentially hacking up my own 'getter-nocheck'.

If I had used the normal getters in the expansion of 'modified-copy',
then (modified-copy s ((foo-x) 'new)) would expand to:

  (make-struct foo 0
(foo-q s) (foo-r s) (foo-s s) (foo-t s) (foo-u s)
(foo-v s) (foo-w s) 'new  (foo-y s) (foo-z s))

and each of those getter uses would include a type-check in their
expansions.  As you suggested, I instead wrap a single check around the
whole thing and then effectively use (foo-*-nocheck s) instead, by using
'struct-ref' directly.

This example is intended to convince you that 'nocheck' variants of
struct accessors are important as a base upon which to build efficient
higher-level constructs, at least for our own internal use.

 Every time ‘car’ is used, there’s a type-check that users cannot
 eliminate.  IMO, if it were to be eliminated, it should be via
 orthogonal means, not via the API: for instance, when the compiler knows
 the check would always pass at run-time, or with (declare (unsafe)), or
 with ‘-DSCM_RECKLESS’.  A ‘car-nocheck’ wouldn’t be convenient, would it?
 :-)

Agreed, but in that case, redundant type tags checks _can_ be optimized
out by a compiler, because those tags are not mutable.  Unfortunately,
the struct vtable pointers _are_ mutable, and in fact are mutated in
practice, so the compiler cannot safely eliminate struct-vtable checks.

 I’ll let you see whether/how you can borrow from this in your code, if
 that’s fine with you.

Okay, will do.

Thanks!
  Mark



Re: Functional record setters, a different approach

2012-04-11 Thread Mark H Weaver
I rushed out my last email because I didn't want anyone else to waste
time working on the same functionality.  Needless to say, this is very
preliminary work.  A few errata in my last email:

   (define-tagged-inlinable (key value) ... (name formals ...) body ...)

There's a missing pair of parens around (key value) ... here.

 The public interface I've created is quite a bit different than what
 we've been discussing so far.  I'm open to changing it, but here's what
 the attached patch currently exports from (srfi srfi-9 gnu):

   (modified-copy struct-expr (field-path expr) ...)
   (modified-copy-nocheck struct-expr (field-path expr) ...)

 where field-path is of the form (field ...)

The path is actually a list of getters, not field names.

   scheme@(guile-user) (modified-copy a
  ((get-x get-i) 10)
  ((get-y) 14)
  ((get-x get-j get-y) 12))

Needless to say, I suck at marketing :)  These record definitions are
what I used for testing (taken from srfi-9.test), but this syntax looks
very bad when the getters are named like this.  It looks much better
when the getters have names like the ones Ludovic chose for his
examples, e.g. 'person-age', 'person-address', 'address-street',
'address-city', etc.

 --- a/test-suite/tests/srfi-9.test
 +++ b/test-suite/tests/srfi-9.test
 @@ -29,7 +29,7 @@
(x get-x) (y get-y set-y!))
  
  (define-record-type :bar (make-bar i j) bar? 
 -  (i get-i) (i get-j set-j!))
 +  (i get-i) (j get-j set-j!))
  
  (define f (make-foo 1))
  (set-y! f 2)

Note that I improved the compile-time error checking to the existing
srfi-9 macros, which called attention to the mistake in srfi-9.test
above.  Previously, our srfi-9 implementation silently accepted
duplicate field names.

 Mark



Re: Functional record setters, a different approach

2012-04-11 Thread Mark H Weaver
I wrote:
 I rushed out my last email because I didn't want anyone else to waste
 time working on the same functionality.

More importantly, I just realized that the patch I sent out included
only part of my changes.  It was missing my initial work to clean up the
srfi-9 code before I started adding the new functionality.  Here's the
complete patch.  Apologies for the wasted bandwidth :-(

Mark


diff --git a/module/srfi/srfi-9.scm b/module/srfi/srfi-9.scm
index da71d1e..866d28b 100644
--- a/module/srfi/srfi-9.scm
+++ b/module/srfi/srfi-9.scm
@@ -29,8 +29,8 @@
 ;; predicate name
 ;; field spec ...)
 ;;
-;;  field spec - (field tag accessor name)
-;;   - (field tag accessor name modifier name)
+;;  field spec - (field tag getter name)
+;;   - (field tag getter name setter name)
 ;;
 ;;  field tag - identifier
 ;;  ... name  - identifier
@@ -60,7 +60,7 @@
 
 (define-module (srfi srfi-9)
   #:use-module (srfi srfi-1)
-  #:export (define-record-type))
+  #:export-syntax (define-record-type))
 
 (cond-expand-provide (current-module) '(srfi-9))
 
@@ -68,8 +68,26 @@
 ;; because the public one has a different `make-procedure-name', so
 ;; using it would require users to recompile code that uses SRFI-9.  See
 ;; http://lists.gnu.org/archive/html/guile-devel/2011-04/msg00111.html.
+;;
+
+(define-syntax-rule (define-inlinable (name formals ...) body ...)
+  (define-tagged-inlinable () (name formals ...) body ...))
+
+;; 'define-tagged-inlinable' has an additional feature: it stores a map
+;; of keys to values that can be retrieved at expansion time.  This is
+;; currently used to retrieve the rtd id, field index, and record copier
+;; macro for an arbitrary getter.
+
+(define %%type #f)   ; a private syntax literal
+(define-syntax-rule (getter-type getter) (getter () %%type))
 
-(define-syntax define-inlinable
+(define %%index #f)  ; a private syntax literal
+(define-syntax-rule (getter-index getter) (getter () %%index))
+
+(define %%copier #f) ; a private syntax literal
+(define-syntax-rule (getter-copier getter) (getter () %%copier))
+
+(define-syntax define-tagged-inlinable
   (lambda (x)
 (define (make-procedure-name name)
   (datum-syntax name
@@ -77,7 +95,7 @@
 '-procedure)))
 
 (syntax-case x ()
-  ((_ (name formals ...) body ...)
+  ((_ ((key value) ...) (name formals ...) body ...)
(identifier? #'name)
(with-syntax ((proc-name  (make-procedure-name #'name))
  ((args ...) (generate-temporaries #'(formals ...
@@ -86,7 +104,8 @@
body ...)
  (define-syntax name
(lambda (x)
- (syntax-case x ()
+ (syntax-case x (key ...)
+   ((_ () key) #'value) ...
((_ args ...)
 #'((lambda (formals ...)
  body ...)
@@ -109,90 +128,139 @@
   (loop (cdr fields) (+ 1 off)
   (display  p))
 
+(define (throw-bad-struct s who)
+  (throw 'wrong-type-arg who
+ Wrong type argument: ~S (list s)
+ (list s)))
+
+(define (make-copier-id type-name)
+  (datum-syntax type-name
+ (symbol-append '%% (syntax-datum type-name)
+'-modified-copy)))
+
+(define-syntax %%modified-copy
+  (lambda (x)
+(syntax-case x ()
+  ((_ type-name (getter-id ...) check? s (getter expr) ...)
+   (every identifier? #'(getter ...))
+   (let ((copier-name (syntax-datum (make-copier-id #'type-name)))
+ (getter+exprs #'((getter expr) ...)))
+ (define (lookup id default-expr)
+   (let ((results
+  (filter (lambda (g+e)
+(free-identifier=? id (car g+e)))
+  getter+exprs)))
+ (case (length results)
+   ((0) default-expr)
+   ((1) (cadar results))
+   (else (syntax-violation
+  copier-name duplicate getter x id)
+ (for-each (lambda (id)
+ (or (find (lambda (getter-id)
+ (free-identifier=? id getter-id))
+   #'(getter-id ...))
+ (syntax-violation
+  copier-name unknown getter x id)))
+   #'(getter ...))
+ (with-syntax ((unsafe-expr
+#`(make-struct
+   type-name 0
+   #,@(map (lambda (getter index)
+ (lookup getter #`(struct-ref s #,index)))
+   #'(getter-id ...)
+   (iota (length #'(getter-id ...)))
+   (if (syntax-datum #'check?)
+   #`(if (eq? (struct-vtable s) type-name)
+ unsafe-expr
+ (throw-bad-struct
+  s '#,(datum-syntax 

Re: Functional record setters, a different approach

2012-04-11 Thread Ludovic Courtès
Mark H Weaver m...@netris.org skribis:

 +((_ check? struct-expr ((getter . rest) expr) ...)
 + ;;
 + ;; FIXME: Improve compile-time error reporting:
 + ;;   1. report an error if any getter-path is a
 + ;;  prefix of any other getter-path.
 + ;;   2. report an error if the initial getters
 + ;;  do not all belong to the same record type.
 + ;;
 + ;; forest : (tree ...)
 + ;;   tree : (getter (rest . expr) ...)
 + (let ((forest
 +(fold (lambda (g r e forest)
 +(cond ((find (lambda (tree)
 +   (free-identifier=? g (car tree)))
 + forest)
 +   = (lambda (tree)
 +(cons (cons g (cons (cons r e)
 +(cdr tree)))
 +  (delq tree forest
 +  (else (cons (list g (cons r e))
 +  forest
 +  '()
 +  #'(getter ...)
 +  #'(rest ...)
 +  #'(expr ...

BTW this will need some more comments ;-), and perhaps splitting in
several functions for clarity.  Using SRFI-1 alists and ‘match’ may help
as well.  WDYT?

(I often find myself avoiding occurrences of ‘car’, ‘cdr’  co. in my
code these days.)

FWIW I was using this approach to represent the tree of accessors:

(define (field-tree fields)
  ;; Given FIELDS, a list of field-accessor-lists, return a tree
  ;; that groups together FIELDS by prefix.  Example:
  ;;   FIELDS:  ((f1 f2 f3) (f1 f4))
  ;;   RESULT:  ((f1 (f2 (f3)) (f4)))
  (define (insert obj tree)
(match obj
  ((head tail ...)
   (let ((sub (or (assoc-ref tree head) '(
 (cons (cons head (insert tail sub))
   (alist-delete head tree
  (()
   tree)))

  (fold-right insert '() fields))

Thanks,
Ludo’.