Hello
So, as I've discussed on IRC, I want to get back into module system development. And as mentioned, I want to concentrate on "from-scratch matters" for now to help make existing module system development efforts' interoperation and further development possible.
What are my aims?
- having a base on which I can write own modularization approaches if I'd like to.
- being able to use code written for other module systems.
- study how that interoperation between the various approaches can be made to work.
- work together with all other interested parties; "as I see fit", at least. The only thing I can say is that I'll try to avoid to tie myself too much into particular usage perspectives; for me Scheme (or a subset thereof, we'll see) is a means to layer languages on, and I want to make this layering as easily workable as possible.
And I'm certainly going to focus development on Gambit as the underlying system, for the foreseeable future.
(What we've once written in the wiki is still true for me: http://dynamo.iro.umontreal.ca/~gambit/wiki/index.php/Module_System )
So what do I plan to work on at first?
I think that the most important thing right now is more flexibility/power in the compilation of code pieces. What we have now is |compile-file| (and of course batch compilation) and |load|, for compilation; and we have |eval| which is the combination of those two for interpretation.
|compile-file| is currently pretty much tied to a "an output file has an input file" philosophy. You can define macros before compiling a file, and add preludes, and even use the c#expand-source and ##expand-source hooks to get to transform the code completely on your own, but it still assumes that there is an input file, whose path you need to give to |compile-file| and which has to exist since tha procedure is going to read from it.
Sometimes you want to build an object file from code coming from multiple input files (in a more practical way than using macro definitions in the compiler environment, I think); and sometimes you want to build multiple object files from one input file, for example for module parametrization, or for splitting an input file containing test cases intermixed with normal code into two object files so that you can (build and) run the tests separately from the normal case.
Creating binary code in the filesystem (as does Gambit) instead of directly in memory has some advantages: the code blobs can be shared amongst processes, and they don't have to be rebuilt (the filesystem is automatically a cache). But we want that as transparent and easy as possible, too. |compile-file| and |load| is quite good already but those don't address the following things in their current form:
- recompilation only if needed - not depend on *input files* - recompilation of single procedures by entering them in the repl
I want a compile-expression procedure which takes an S-expression as input (maybe later on, we'll also want it a level deeper, after lexical parsing). Gambit does have it's internal form of location-annotated S-expressions already which is just fine; they can be read from files using ##read-expr-from-port, for example.
I also want that this procedure *only* depends on the input it is being fed; i.e. it's a pure function in the sense that the resulting object file only depends on it's inputs. It being pure, you can build a digest of it's inputs, and count on every output it generated being the same if the digest is the same. Thus you can use that digest string as object filename, and if that file already exists, it need not be generated (compiled), only (re)loaded.
I've started to look into implementing this, but it wasn't easy enough to do in the couple of partial days I've had to invest so far; I think especially the part of reloading object files will need some core work: an object file that has been loaded already doesn't need to be reloaded, just re-"linked", or whatever it's being called what Gambit does after loading an object file to make it's toplevel variables visible in the global symbol table. Reloading an object file which has already been loaded once means it should just relink those bindings.
Once those are there, you could just redefine |compile-file| and |load| in terms of those. But use digest strings as object file names instead, maybe, or whatever you'd like; or write |compile-file-and-load-if-changed|, or whatever you'd like to use in your module implementation, anyway. File dependency algorithms etc. can then even more easily be built on top.
And then the next level can be tackled: lexical parsing and macro implementation; of course Gambit has those already built in for the lowlevel kind of macros, and maybe we can use part of that code for the purpose; parsed trees could then, so I hope, be handled by hygienic code merging algorithms, and there we might get chances to make different approaches work together (i.e. code of one module system can use macros of another one); we'll see.
What I'm also thinking about is that for working with code, we may want a simpler version of Scheme, one which doesn't know about the various syntactical sugar forms (like, there is no let or let*, just define[2] and lambda; letrec may be unavoidable). This should make writing macro algorithms easier (or manually written lowlevel macros), right?, or at least other code analyzers (type inferencers, documentation extraction,..). Kind of an even lower language in a syntactic tower. I don't know what other Scheme systems do in this respect but I've read a little bit about GHC's 'core' language, I'm imagining something in this direction. (And at the same time that it is simpler than R5RS Scheme it would also have more core 'ops', namely unsafe ones -- meant to be used safely by using type inferencers/ systems. But from the point of the language, those would still just be procedures.)
([2] well actually I'm not sure yet whether define could/should be reduced to lambda and letrec's too, and some sort of more flexible handling than binding them to "toplevel" variables.)
That's it for now. I'll tell you once I've got some code written; in the mean time, I'm glad hearing your comments.
Christian.
Afficher les réponses par date
Hi.
That sounds good, especially to me since what you are describing has virtually no overlap with what I have written does. :)
What I'm also thinking about is that for working with code, we may want a simpler version of Scheme, one which doesn't know about the various syntactical sugar forms (like, there is no let or let*, just define[2] and lambda; letrec may be unavoidable). This should make writing macro algorithms easier (or manually written lowlevel macros), right?, or at least other code analyzers (type inferencers, documentation extraction,..).
I agree with this idea. However, having written a rather complete macro hygiene system, my experience is this: defines seem to be unavoidable in the top level, but for everything else they should be avoided when writing macro algorithms. letrecs are far easier to analyze than defines because of their more rigid structure.
This is of course assuming we're sticking to R5RS way of things, where define at the top level isn't at all the same as define within a scope.
/Per
gambit-modules-list@iro.umontreal.ca