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)
|
Le Thu, 27 Oct 2022 10:00:17 +0200, Marc Nieper-Wißkirchen <xxxxxx@gmail.com> a écrit : Hi, More thoughts to share. Trying to be short. 1. OO langages mostly allow multiple constructors -with different signatures- (many of it -C++, Java- use unique name and overloading, shit on it -just may silly opinion-) Anyway it is used so it is of interest. Need example? (define-record-type <sbuf> (!sbuf! len vec) sbuf? (len sbuf-length sbuf-set-length!) (vec sbuf-buffer)) (define (make-sbuf-with-capacity capacity) (!sbuf! 0 (make-vector capacity #\null))) (define (make-sbuf-from-string string) (!sbuf! (string-length string) (string->vector string))) My understanding is that protocol of r6rs doesn't give any facility for that case. It is intended for inheritance only but restrict its usage to a single signature (but okay we can use case-lambda, perhaps). 2. define-record-type is not OO. The issues solved by protocol remain for methods that are not constructors and are not solved. So the need is for constructor only. I imagine a procedure that wraps children fields to provide a parent constructor. It could solve. Let use R6RS's abstract example (p18) with SRFI-136 formalism and with some functions foo, bar1 and bar2. (define-record-type <parent> !parent! parent? (v1 v1) (v2 v2) (v3 v3)) (define-record-type (<child> <parent>) !child! child? (x1 x1) (x2 x2) (x3 x3) (x4 x4)) (define (make-parent v1 v2 . build) (let ((make (if (null? build) !parent! (car build)))) (make v1 v2 (foo v1 v2)))) (define (make-child x1 x2 x3 v1) (let ((x4 (bar1 x1 x2 x3 v1)) (v2 (bar2 x1 x2 x3 v1))) (make-parent v1 v2 (lambda (v1 v2 v3) (!child! v1 v2 v3 x1 x2 x3 x4))))) Works but it is not perfect. It has to be forged by the programmer and included in the design, it does not comes for free. What comes for free is the constructor !child!. Instead of (lambda (v1 v2 v3) (!child! v1 v2 v3 x1 x2 x3 x4)), a facility function could exist: ((record-type-wrap-creator-for-parent <child>) x1 x2 x3 x4) It is very similar in the approach to protocol. Maybe more flexible. And, also, I'm very sorry but I am very happy with the generativity of define-record-type in R7RS. It is clear and regular with other parts of the language. Best regards José Bollo > Thank you for your email, José. > > I do not intend to change names (e.g. "extend" instead of "parent" or > "composition" instead of "inheritance"). If we were in a void when > designing this SRFI, using the best names we could think of would be > the right thing. However, the advantages of alternative names are > minor (and would clash with the tradition set in SRFI 57/SRFI > 76/R6RS/SRFI 99/SRFI 131/SRFI 136/SRFI 150) that infringing backward > compatibility is no option. > > You write that you don't care about it because you have no legacy > code. This is not the case with many other Scheme users who regularly > use the R6RS record system. The good news is that what you have been > using won't be touched by this SRFI. > > You also wrote that R6RS records are "over-complicated". This is > subjective (but a valid opinion), but you may be interested in what I > wrote here: https://srfi-email.schemers.org/srfi-237/msg/21064822/. > The next draft of this SRFI will show how to simplify the R6RS record > system to address the primary concern of SRFI 99 but without > sacrificing its expressive power. > > As for the sq example, it is a question of design and what abstract > data type you actually want to model. The record facility of this > SRFI gives you the option to realize an abstraction barrier. If a > program attempts to breach an abstraction barrier (that is part of > your design), the system should be able to disallow this. (Of course, > your design may be wrong, but this is a different question > altogether.) > > The expressiveness of a programming language is not measured by what > you can do with a given piece of code. Otherwise, machine language > would be the most expressive one. (There are different notions of > "expressiveness"; I tried to use one relevant to Scheme and > programming language theory.) > > Thanks, > > Marc > > Am Do., 27. Okt. 2022 um 09:30 Uhr schrieb José Bollo > <xxxxxx@nonadev.net>: > > > > 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. > >