Truly unifying R6RS and R7RS Daphne Preston-Kendal (04 Oct 2022 18:22 UTC)
Re: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (04 Oct 2022 19:16 UTC)
Re: Truly unifying R6RS and R7RS John Cowan (06 Oct 2022 20:30 UTC)
Re: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (06 Oct 2022 21:11 UTC)
Re: Truly unifying R6RS and R7RS John Cowan (07 Oct 2022 01:33 UTC)
Re: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (07 Oct 2022 08:20 UTC)
Re: Truly unifying R6RS and R7RS Arthur A. Gleckler (07 Oct 2022 18:22 UTC)
Re: Truly unifying R6RS and R7RS John Cowan (07 Oct 2022 22:02 UTC)
Re: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (08 Oct 2022 10:37 UTC)
Re: Truly unifying R6RS and R7RS José Bollo (27 Oct 2022 07:30 UTC)
Re: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (27 Oct 2022 08:00 UTC)
Re: Truly unifying R6RS and R7RS José Bollo (01 Nov 2022 14:22 UTC)
Re: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (01 Nov 2022 14:34 UTC)
Re: Truly unifying R6RS and R7RS José Bollo (03 Nov 2022 08:42 UTC)
(missing)
Fwd: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (03 Nov 2022 13:18 UTC)
Re: Truly unifying R6RS and R7RS José Bollo (26 Nov 2022 10:02 UTC)
Re: Truly unifying R6RS and R7RS Marc Nieper-Wißkirchen (26 Nov 2022 17:26 UTC)

Re: Truly unifying R6RS and R7RS José Bollo 27 Oct 2022 07:29 UTC

Le Fri, 7 Oct 2022 10:20:19 +0200,
Marc Nieper-Wißkirchen <xxxxxx@gmail.com> a écrit :

Hi all,

I like your mail Marc, it explains very well the problem.

However I'd like to share some thought about it. But before that, let
me tell you that my opinion is to let R6 records vanishing because it is
over complicated and promote implicit naming.

[...]

> First of all, a record type should not be mistaken for a class as it
> is known from object-oriented languages like Java. (I prepend this
> comment because much of what is said in favor and against class
> systems and inheritance in typical OOP languages does not apply to
> Scheme's record-type system.) A record type does not offer the notion
> of methods, let alone virtual methods (although the record-type
> facility can be used to implement more OOP-style oriented class
> facilities, of course). As far as R7RS-small and R6RS without
> inheritance are concerned, a record is just like a vector whose
> elements are not accessed by index but by dedicated procedures and
> whose type is disjoint from the rest of Scheme types. This allows the
> creation of new opaque Scheme types whose implementation details are
> encapsulated so that the resulting type (together with the primitive
> procedures accessing it) can be viewed as the realization of the
> concept of an abstract data type in a dynamic language like Scheme.
> The vast majority of uses of the record-type facility of the small
> language (and of R6RS) are in this spirit. (For evidence for this, one
> just has to look at the significant number of SRFIs introducing new
> types into the Scheme language.)
>
> Now let us add inheritance.

I'm thinking that the main problem is here. When you write it, your
intent is to really add some inheritance. And in the example you are
using, this is shown because you expect to preserve some kind of class
invariant.

It may reveal an error in SRFI where words "inheritance" and
"extend/extension" have to be weighted in the balance and used
correctly.

> The underlying model is the same in the
> context of R6RS or any of the various other proposals (SRFI 99, SRFI
> 131, SRFI 136, SRFI 150), so this simplifies the discussion (and
> allows me to use the R6RS surface syntax with no loss of generality).
> Inheritance is just the creation of a new type that is composed of an
> existing (record) type and a bunch of new fields, where it is arranged
> so that the accessors and mutators and the predicate of the existing
> type apply to the composed type as well. In principle, we could
> therefore go without inheritance by making the composition explicit.
> So, for example, instead of
>
> (define-record-type foo (fields x))
> (define-record-type bar (parent foo) (fields y))

Here the use of "parent". Why not "extend" or "include"? It is a
vocabulary details but I think that it can change how things are
perceived: here you expect paradigms of OOP to apply.

Okay it keeps R6 stuff unchanged. And it seems to be a valuable
motivation. But I don"t care about it because [1] I have no legacy code
and [2] scheme language is enough flexible to process the case using an
import "r6rs".

> we would write
>
> (define-record-type (foo make-foo %foo?)
>   (fields (immutable x %foo-x)))
> (define-record-type bar
>   (fields (immutable foo %bar-foo) y)
>   (protocol
>     (lambda (p)
>       (lambda (x y)
>         (p (make-foo x) y)))))
> (define (foo? obj) (or (%foo? obj) (bar? obj)))
> (define (foo-x foo) (if (%foo? foo) (%foo-x foo) (%foo-x (%bar-foo
> foo))))
>
> The reason why we still want inheritance (in the style of the Scheme
> record-type facilities) although it is not much more than composition
> is at least two-fold: The above code with explicit composition adds
> another indirection as can be seen from the definition of `foo-x`.
> This inefficiency adds up for each further composition (compared to
> this, the single-inheritance record-type facility can be implemented
> so that parent- and child-type fields are arranged linearly in
> memory). Moreover, the contortions needed in the above code so that
> the accessors and mutators and the type predicate of the "foo" type
> apply to the "bar" type do not scale well.

Here again question of vocabulary, inheritance is connoted as
object-oriented. By the way, the word composition is interesting but
also subject to discussion. In a later mail your wrote:

>------BEGIN OF THE INCLUDED MAIL-------------------
> Le Sat, 8 Oct 2022 12:37:19 +0200,
> Marc Nieper-Wißkirchen <xxxxxx@gmail.com> a écrit :
> ....
> Inlining cannot eliminate the cost of the extra indirection that is
> necessary when the types/fields are composed by hand and not using the
> inheritance mechanism. It's the difference between
>
> struct foo
> {
>   /* ... */
> };
>
> struct bar
> {
>   struct foo *x;
>   /* ... */
> };
>
> and
>
> struct foo
> {
>   /* ... */
> };
>
> struct bar
> {
>   struct foo x;
>   /* ... */
> };
> ....
>------END OF THE INCLUDED MAIL-------------------

For me it show the ambiguity of the word "composition". I read in
literature use of the terms "extend", "compose" and "aggregate". I am
not sure about what are the exact terms to use. For me extend is the
mechanism without pointer and aggregate with pointer.

> The straightforward implementation of composition above (the second
> code example) has one crucial feature we want to carry over to the
> composition-through-inheritance model of the Scheme record-type
> facilities.  The "foo" type is an abstract data type (in the sense
> from the beginning of my explanation) from the viewpoint of the
> composing data type "bar".  To illustrate this, let us look at a
> similar (but let me skip the contortions mentioned above because they
> are not relevant here):
>
> (define-record-type sq (fields (immutable x sq-ref) (immutable y
> sq-sqrt-ref)) (protocol
>     (lambda (p)
>       (lambda (x) (p x (sqrt x))))))
>
> The "sq" type models a number, of which I can take a square root. The
> calculation of the square root is cached, but this is an
> implementation detail and transparent to the user of this record type
> (viewed as the reflection of an abstract data type). In the explicit
> composition model, say when we want to implement a "colored-sq", the
> composing type needs to access the "sq" type solely through its public
> interface. Thus the abstraction barrier is not breached. (This is an
> essential point because the definition and implementation of the "sq"
> type may be in a different library (or package) so the implementer of
> the "colored-sq" type would or should not know about the caching of
> the square root.
>
> With custom constructors in the sense of R6RS, this abstraction
> barrier is preserved.  A possible definition of the "colored-sq" type
> would then be
>
> (define-record-type colored-sq (fields color)
>   (parent sq)
>   (protocol
>     (lambda (n)
>       (lambda (x c)
>         ((n x) c)))))
>
> As we can see, we do not need to access any implementation details of
> the "sq" type. Without the protocol device, this is not possible,
> though, and this is the reason for custom protocols: The definition of
> the "sq" type itself is not a problem. We can instead write
>
> (define-record-type (sq %make-sq sq?) (fields (immutable x sq-ref)
> (immutable y sq-sqrt-ref)))
> (define (make-sq x) (%sq x (sqrt x)))
>
> The problem arises as soon as we want to inherit from it:
>
> (define-record-type (colored-sq %make-colored-sq colored-sq?)
>   (fields color) (parent sq))
>
> (define (make-colored-sq x c)
>   (%make-colored-sq x (sqrt y) c))
>
> Obviously, we need to access what should be implementation details of
> the "sq" type here. The `make-sq` procedure, which should have
> provided us with the abstraction cannot be used here.

Here is the knot of the argument. The procedure make-colored-sq is
perfectly correct and clearly implement what is expected. But has you
wrote it should use some knowledge that might be hidden. And I agree
that some kind of solution should be offered to programmers to access
that issue.

But the point of vue that you have is to keep the invariant that sq is
the sq defined in sq. What if the programmer expects the opposite? The
assumption that the invariant is kept is valuable and natural. It is
the most common case. But making it a keystone is not good in my mind.
Let be flexible and don't close doors.

So conversely, if you are using protocol as proposed, it seems
difficult to change the value of sq in "inheriter".

I'm stopping here with that argument. I'm sure that something has to be
done to improve SRFI-136 in order to allow calling constuctor in
constructor. That could be a good issue for the problem that you
perfectly exposed.

Best regards
José

> One could try to repair it by having the "sq" package export a
> procedure like
>
> (define (sq-init! sq x)
>   (sq-set! sq x)
>   (sq-square-set! sq (sqrt x)))
>
> Moreover, a restricted SRFI 9-style custom constructor would be used
> for "sq":
>
> (define-record-type sq
>   (%make-sq)
>   sq?
>   (x sq-ref)
>   (y sq-square-ref))
>
> This way, the `make-colored-sq` procedure can be defined by
>
> (define (make-colored-sq x c)
>   (define rec (%make-colored-sq c))
>   (sq-init! rec x)
>   x)
>
> This, however, is a non-solution for two reasons: First of all, it
> forces at least the "y" field of the "sq" type to be mutable, which
> will prevent some possible optimizations (employed by Chez, for
> example).  Moreover, this "solution" still breaks the abstraction
> barrier around the "sq" package because it is would be possible to
> construct broken objects of the "sq" type (because the invariant that
> the "y" field holds the square root of the "x" field is broken).
>
> I can think of correct solutions outside of R6RS-syle custom protocols
> to maintain the abstraction barrier around the "sq" package. but these
> would be no simpler than the R6RS-solution  of custom protocols
> (instead of exporting the record name "sq" to be used in record-type
> definitions of child types, the "sq" package would have to define and
> export a syntax like "define-sq-type" that encapsulates everything
> related to defining child types). And such a solution would not be
> integrated into the procedural layer.
>
> I hope the above rationale explains the raison d'être of custom
> protocols for R6RS record types. Unrelated to this, making the
> definition of custom constructors as simple as possible has another
> advantage: type checking. So for example,
>
> (define-record-type point (fields x y)
>   (protocol
>     (lambda (p)
>       (lambda (x y)
>         (assert (real? x))
>         (assert (real? y))
>         (p x y)))))
>
> is much better than `(define-record-type point (fields x y))` to
> define a type of a point in the cartesian plane. With the latter
> definition, I can create points that are not true points and when I
> later get errors, e.g. when I attempt to calculate the distance
> between two points, the location where the error becomes apparent is
> not where the error originated, making debugging hard.  Probably the
> majority of record types have some constraints for the arguments of
> their constructors, and it is good when the language suggests checking
> them at construction time.
>
> >> I am not convinced that is necessary to provide the second form of
> >> `define-record-type` with all bells and whistles as long as
> >> R7RS-small compatibility is there
> >
> >
> > It is of course not *necessary*, but it accommodates R7RS-small
> > users who wish to make use of one of the R6RS features without
> > learning the entirely new syntax.
>
> All right.
>
> [...]
>
> > Yes.  So let's adopt the p-list-style extension to R7RS:
> > (define-record-type (foo parent bar) ...)
>
> Yes.
>
> >>
> >>
> >> But what would it mean to give a protocol (name)?
> >
> >
> > All right, we can do without that (see below).  So `parent`,
> > `sealed`, `opaque`, and `nongenerative` would be allowed in any
> > order.
>
> See above why this would still make the SRFI 9-style syntax
> considerably inferior to the R6R-style syntax.
>
> >> I am not sure how much this still looks like SRFI 9-syntax when
> >> there are a lot of entries next to the record name. Would it
> >> really be better (for SRFI 9-users) than the first syntax given in
> >> this SRFI?
> >
> > Yes, I think so:
> >
> > (define-record-type foo
> >   (make-foo bar baz)
> >   foo?
> >   (bar foo-bar-ref)
> >   (baz foo-baz-ref foo-baz-set!))
> >
> > (define-record-type (extfoo parent foo)
> >   (make-extfoo quux)
> >    extfoo?
> >    (quux foo-quux-ref))
> >
> > If this extension to R7RS-small syntax is accepted, the question
> > remaining is whether the purely syntactic features of SRFI-99
> > should also be provided.  I think the answer is no, per SRFI 131.
> > The reason is not, as in SRFI 131, in order to allow a syntax-rules
> > implementation, but simply because SRFI 9 / R7RS-small users are
> > already accustomed to providing explicit names.
>
> I think this is a good suggestion. Those who want implicit names (for
> whatever reasons) can always use the R6RS-style form of the syntax.
> And those like Daphne who "hate" implicit names get a domain where
> explicit names are guaranteed.
>
> >
> >>
> >> In any case, however, the problem of interpreting field names in
> >> SRFI 9-style constructors in child record-type definitions
> >> remains. They cannot refer to parent fields (as SRFI 99 says) for
> >> R7RS-small compatibility, so the only solution seems to be that
> >> the names correspond solely to child fields and that the parent
> >> constructor's formal arguments are implicitly added at the
> >> beginning.
> >
> >
> > The bulk of all constructors include all the arguments anyway; the
> > typical case is to hide the complete constructor in a library and
> > export an ordinary function that does whatever's necessary to set
> > up the fields correctly.
> >>
> >> (define-record-type colored-point
> >>   (nongenerative) (opaque #t)
> >>   (parent point)
> >>   (fields color)
> >>   (protocol
> >>     (lambda (n)
> >>       (lambda (x y c)
> >>         (assert (memq c '(red green blue))
> >>         ((n x y) c)))))
> >>
> >> What would a translation of this into SRFI 9-style look like?
> >
> >
> > It wouldn't; see above.
>
> See above why there should be a way to express it.
>
> But I think I have an idea of how to incorporate custom protocols in
> SRFI 9-style definitions:
>
> (define-record-type (sq protocol (lambda (x) (make-sq x (sqrt x))))
>   (make-sq x y)
>   sq?
>   (x sq-ref)
>   (y sq-sqrt-ref))
>
> (define-record-type (colored-sq parent sq protocol (lambda (x c)
> (assert (color? c)) ((make-colored-sq x) c))
>   ((make-colored-sq x) c)
>   colored-sq?
>   (c colored-sq-color))
>
> Here, "x" is a dummy placeholder.  The above translates to the
> R6RS-style definitions:
>
> (define-record-type (sq make-sq sq?)
>   (fields (immutable x sq-ref) (immutable y sq-square-ref))
>   (protocol
>     (lambda (p)
>       (let ((make-sq (lambda (x y) (p x y))))
>         (lambda (x) (make-sq x (sqrt x)))))))
>
> (define-record-type (colored-sq make-colored-sq colored-sq?)
>   (fields (immutable c colored-sq-color))
>   (protocol
>     (lambda (n)
>       (let ((make-colored-sq (lambda (x) (lambda (c) ((n x) c)))
>         (lambda (x c) (assert (color? c)) ((make-colored-sq x) c))))))
>
> I hope, the translation is obvious enough to understand from the
> examples.  The disadvantage of this proposal is that the constructor
> subform in the SRFI-9-style syntax is no longer a template for the
> actually defined constructor.