2013/05/09
Export-time renaming
Recently I implemented rename feature in the export
form.
With the import options (see Import options: part one and Import options: part two),
this completes the infrastructure to support R[67]RS's modules
on top of our module system.
The syntax of export-time renaming is the same as R[67]RS. If you have the following module:
(define-module example1 (export (rename foo boo)) (define foo 3))
Then the name foo
in example1 module can be
referred as boo
from the modules that imports it.
gosh> (import example1) #<undef> gosh> boo 1
* * *
During the course,
I changed ScmModule
structure to manage the exported symbols.
A module is a map from names (symbols) to locations (global locations, or GLOCs).Essentially it's just a hashtable. Visibility (whether a name can be seen from others that import this module) is an auxiliary information.
Initially ScmModule had a list of exported symbols. When an identifier was looked up, we scanned the exported list of each imported modules, and if we found a match, we looked up the hashtable to get the corresponding GLOC.
Obviously this didn't scale when export list got longer. So several years ago I switched to put a flag in each binding to indicate whether the symbol was exported. Then I only needed to look up a hashtable and check a flag. But what does the binding mean? Conceptually, it is the association of a name and a GLOC, which is a hash table entry in our implementation. There's no place to add a flag in a hash table entry itself. So I made GLOC to have the exported flag.
There I stepped into a shady area. If a GLOC can be shared among different modules, there might be a case that it's exported in one module but not in another. We didn't have such cases in good old days, but the import options introduced GLOC-sharing cases. It wasn't a problem so far, since import options operates only on exported symbols. Yet this kind of hack reeks, and may bites back down the road.
Then it comes to the export renaming. A GLOC can now have multiple names, and we need to choose which name to look up depending on whether we're searching exported symbols or not. A straightforward way is to have two tables, one for exported names, and another for internal names.
And if we have a separate table for exported names, then the mere fact that the name is registered to the table indicates the fact that the name is exported---we don't need an extra flag in GLOC. Yay!
So the flag is removed from GLOC, and a new table for exported names
is added to ScmModule. The module-exports
introspection API
returns a list of exported names for the backward compatibility,
but now it calculates the result list from the exported name table
every time it is called.
There's one caveat, caused by the openness of Gauche module.
In Gauche, a programmer can export symbols of existing modules
at any time, using with-module
. (During development, sometimes
I do (with-module foo.internal (export-all))
so that I can
call internal procedures of foo.internal
easily.)
This wasn't a problem before, since exporting already exported
symbol was just a no-op.
With export-time renaming, it's no longer true. An internal symbol
foo
may have been exported as boo
, but now it can be
exported as voo
. How should this situation be handled?
- Should the previous export be removed? I decided not. It's costly
to search if the internal symbol
foo
has been exported in another name. (we could have a reverse map, but it seems unnecessary complexity.) Plus, any code that counts on the external nameboo
may break. - What if another symbol has already been exported as
voo
? This also would break code that counts on the previousvoo
, but the operation may be intentional (e.g. hot-patching). I assume such case shouldn't happen in normal circumstances, but needed in emergency. So I make a warning issued but allow the meaning of external namevoo
to be updated to point tofoo
.
* * *
I already implemented R7RS module system on top of this, and I'll describe it in the next entry.
Post a comment