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
---------------------
[email protected]
http://web.mac.com/jay_reynolds_freeman (personal web site)



_______________________________________________
Scheme-reports mailing list
[email protected]
http://lists.scheme-reports.org/cgi-bin/mailman/listinfo/scheme-reports

Reply via email to