guile-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Functional record "setters", a different approach


From: Mark H Weaver
Subject: Re: Functional record "setters", a different approach
Date: Thu, 12 Apr 2012 11:04:13 -0400
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.0.92 (gnu/linux)

Hi Ludovic!

address@hidden (Ludovic Courtès) writes:
> Mark H Weaver <address@hidden> 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 cherry-pick
ideas/code from mine?  Either way is fine with me.

    Thanks!
      Mark



reply via email to

[Prev in Thread] Current Thread [Next in Thread]