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

[Scheme-reports] General comments on the draft WG1 R7 report.

    I am Jay Reynolds Freeman (no relation to "Saurik").  I am the implementor of two R5 Schemes.  They are Wraith Scheme (open-source/shareware, for the Macintosh) and Pixie Scheme III (open-source/cheap in the App Store, for the iPad).  I have been implementing Schemes and making them available for, good grief, almost 25 years.  I have a number of comments to make about the R7 draft report, and have been wondering how to jump in, and have decided that a timely belly-flop is better than a graceful dive too late, so here goes ...

    So you will know where I am coming from, I voted against R6 because I thought it was too complicated for me to implement, and I have not tried to implement it for that same reason.  I am not opposed to anything in R6 in principle, nor am I saying it is too much for one person to implement, I am merely saying that it is too much for *me*.

    I agree with recent comments here about the difficulties of simultaneously (1) making WG1 R7 simple in its own right, (2) making it upward-compatible -- or at least not disastrously incompatible -- with whatever WG2 comes up with, and (3) being judicious about when, how and how much to be backward-compatible with R6 and/or R5.  I commend all the WG1 members for not having simply hidden under the bed till the dust has settled.  I have a few concerns about (1) and (3), and I hope I have constructive suggestions as well.  I can't speak much to (2) because I have no idea what WG2 is up to, and I cannot speak much to (3) because I suspect the main difficulties are where R5 did it one way and R6 did it another way, and as I have indicated, my eyes rather glazed over when I saw the size of the R6 report.

    I have particular concerns about the record system, about the module system, and about exception handling (and let me say that none of my Scheme implementations have a record system, a module system, or exception handling -- I am not trying to sneak in my own work under the guise of constructive criticism).  In any case, let me start with some generalities.  I think that a good strategy for keeping things simple would be:

    (A) For any proposed major subsystem of WG1 R7, require as built-ins to the language no more than a set of constructs which cannot easily be written in the rest of R7 Scheme itself -- these would be things that implementors would have to create in, e.g., the C++ code of the implementation itself.

    (B) Put any other necessary features of any proposed major subsystem into a required "scheme" module of the language, and provide -- either as an appendix or by a link to a repository somewhere -- Scheme source for a bare-bones, minimalist implementation of that module.  (There is plenty of precedent here; I have in mind the derived expression types in section 7.3 of R5, and the example code for "delay" and "promise" in section 6.4 of R5.  Also, many SRFIs could serve as guides for how this kind of approach would work.)

    (C) Put any optional features of any proposed major subsystem into optional "scheme" modules, also perhaps with source code for bare-bones implementations somewhere.

    (D) In a perfect world, large portions of the difference between WG1 R7 and WG2 R7 might thus merely be that modules that were optional in the former were required in the latter.  (In an imperfect world there would be a cat fight between WG1 and WG2.)

    Also with respect to simplicity, I am rather in favor of keeping new syntactic constructs to a minimum.  Although it is true that "define-syntax" and its friends make it easy enough to implement new syntax, nevertheless, each item of new syntax is one more thing for users to remember, and is usually lots and lots more things for implementors to test:  The ease of adding new constructs should not blind us to how bewildering they can look to the uninitiated, and to how much longer, fatter, fussier and harder to maintain they make the implementors' regression suites.

    #### About the record system:  In that context, noting that the WG1 R7 record system appears to have grown  out of SRFI 9, it looks to me as if a reasonable bare-bones requirements for built-ins would be (a) a record abstraction, together with (b) procedures "record?", "make-record", "record-ref" and "record-set!" ("record" -- analagous to "vector" or "string" -- might also be useful), all as defined in SRFI #9.  As SRFI #9 points out, "record" is a simple variant on "vector", so should be easy to install in most implementations.  With these primitives built in, any implementor could use just the rest of the the SRFI #9 code to get a required "record" module going, and anyone -- implementor or user -- could write a better (or at least different) version of the required "record" module, with some expectation that it would be portable among implementations.

    #### About the module system:  I have two worries about the module system, but they are related.  (I have the feeling there is a lot of history about how the "module" system is set up to work; I apologize for not knowing where it came from.)  The WG1 R7 module system is novel in that it seems to go to great lengths to specify just how the code that implements a module should be written.  Doing so means, I think, that it defines more functionality than is strictly necessary, in order to support the requirements for specifying the internals.

    A key point in thinking about modules is that they may be written both by users and by implementors or Scheme wizards.  Their intent may range from simple one-off use by a user to widespread distribution over many implementations.  Thus some freedom in how to go about implementing modules might be useful.

    It seems most straightforward to me to think of a module as an opaque object -- let's call it a procedure for the sake of a definite example -- that has certain behaviors:  Basically, a module is a source of bindings for certain procedures and the like.  As a convenience (and perhaps as an aid for reading documentation and for looking up specific bindings within itself) it provides a list of the default symbols that its contents might reasonably be expected to be bound to in common situations.  It must also have some means to know that other modules upon which it may depend have been loaded, so that it can import any bindings from them that it may need.  The meaning of that last is that if modules foo and bar depend on each other, then since these two modules may not know the location of each others' source files, presumably what one does is something like this:

        load the file for module foo
        load the file for module bar
        tell foo to load in what it needs from
            other modules (just bar in the present case)
        tell bar to load in what it needs from
            other modules (just foo in the present case)

    With these comments in mind, I can give an informal example of how to specify a module system more simply, more or less as follows (many details TBD):

    A module is a procedural object that responds to keywords (symbols) in much the manner of a class with methods.  There are three such keywords, here illustrated informally by example, in the case in which the "scheme 'complex' module" is represented by a procedural object named "complex".

  (complex 'exports)
    ==> (angle magnitude imag-part real-part make-polar make-retangular)

  (complex 'import 'imag-part)
    ==> <the procedural code that you would expect for "imag-part">

So that if you didn't like abbreviations, you could write

  (define imaginary-part (complex 'import 'imag-part))

Now suppose you had two modules "foo" and "bar" with mutual dependencies.  In that case what you would do is

  (load <file for "foo">)  ==> defines a module bound to "foo"; e.g.,
    contains source code like "(define (foo keyword . rest) ...)"
  (load <file for ""bar>)  ==> defines a module bound to "foo".

Then subsequently -- here is the third keyword, "'close" in the sense of closure.

  (foo 'close)
  (bar 'close)

On receipt of 'close, you can imagine code running somewhere inside of "foo" that does something like:

  (set! local-value-for-bar-1 (bar 'import 'bar-1))

in which "local-value-for-bar-1" had been defined in a let somewhere inside the source for "foo".  (In many cases, it would just be "(set! bar-1 (bar 'import 'bar-1)", I am merely making the point that it is possible to use local names if need be.)

    With all modules defined as providing this basic functionality, it would not be hard to have optional modules, with bare-bones source-code implementations provided, that extended it, by providing (eg) more conventional procedural interfaces to import things, and perhaps providing procedures or macros to create modules.

   If modules were defined in terms of functionality this way, there would be many ways to implement them, and I can imagine that different users, with different intents, might well implement them in different ways.  (I did cook up a straw-man implementation before posting this note, and will provide it on request if anyone wants to see it.)

    Before I leave modules, let me consider "cond-expand".  It is clearly based on a SRFI, and that is perhaps a case for leaving it alone, but how about instead requiring each implementation to provide a functionally-accessed alist of features it provided, perhaps as the procedure call (features), which would return, e.g., ((r7rs) (exact-closed) (ratios) (name my-scheme-implementation) ...).  (Such a list might even be alterable in case a module or something added to it.)  Users could then use "assv" and the like on the alist and feed the results directly to cond -- there would be no need for a new syntactic construct.

    #### About exception handling: My worries here are a little vaguer, not least because I find the discussion of exception handling in the draft report sufficiently confusing that I am not sure what it all does.  (Mea culpa, I am sure I will figure it out somehow.)  Yet still I think there is too much stuff here; at least, too much built in.  Java seems to get along with just "try", "catch" and "throw".  Do we really need more mandatory stuff in a language that is supposed to be simple?

    I hope everybody takes these comments as constructive, and I also hope they are timely.  And once again I say that though I do have preferences, none of the approaches here are based on anything in my own implementations; I am not trying to sneak in my own work or make it easier for me to morph Wraith Scheme and Pixie Scheme III into R7 by having R7 adopt what I have done.

--  Jay Reynolds Freeman
http://web.mac.com/jay_reynolds_freeman (personal web site)

Scheme-reports mailing list