I'm excited. Using namespaces for modules seems like a natural and simple idea, and it does work well for functions... but not very well for macros. This morning I had an idea -- and as far as I can tell so far it seems to be working.
First some background. What is a namespace? When an expression such as (+ a b) is compiled, the Arc compiler first checks if a symbol is a local lexical variable, and if it isn't, generates code to get the value of the variable in the global variable namespace.
Conceptually, a namespace is just a table with variable names as keys and the variable values as values. A namespace table can be implemented in different ways such as with a hash table, with an association list, or with a Racket namespace object. As it happens when compiling Arc code into Racket, using a Racket namespace object for Arc's global variables is the fastest implementation to use (I imagine because the Racket folks have worked hard to optimize the Racket compiler), but in my Arc runtime project you can choose a different implementation for global variables if you want to. There's nothing magical about a namespace: it's just a data structure mapping global variable names to variable values.
Since a namespace is conceptually just a table, it could actually appear as a table in Arc, and you could get or set the global variable "x" in a namespace using namespace!x. I haven't implemented this yet (though it wouldn't be very hard), but I do have some functions to get and set namespace values:
What gets interesting is that we can create more than one namespace. Arc code running in one namespace has a different set of global variables than Arc code running in a different namespace: "map" in another namespace could refer to the standard function or it could be set to something different.
In ar a new Arc namespace can be created with "new-arc". This creates a namespace populated with Arc's primitive functions and the Arc compiler, but doesn't have arc.arc loaded yet.
We can get arc.arc loaded with aload, which gives us the usual Arc functions and macros such as map:
Once we've fetched a function out of a different namespace, we can use it like any other function:
We can also evaluate code in a different namespace using eval:
Once a function has been created in another namespace, we can "import" it by copying the value into our namespace:
which of course could be made more succinct by writing an "import" macro.
So the natural question to ask is could we use namespaces for modules? Well, namespaces work great for creating modules for functions. Imagine that we want to import a function foo that calls a helper function bar:
If we aren't going to use bar ourselves, we don't need to import it. foo calls bar in its own namespace, and so we can import and use foo without needing to import bar. In fact we can use bar as a global variable in our namespace, and foo still works calling bar in its namespace:
But this idea doesn't work very well for macros. If I define a macro foo that expands into a helper function bar, just importing foo by itself doesn't work:
The problem here is that the macro foo doesn't expand into the function bar, it expands into the symbol bar. Thus when I expand foo in my namespace, the resulting expression tries to look up bar in my namespace.
Of course I could also import bar. But if I have to import all of foo's prerequisites when all I want is foo, then there's not much point in having modules. I might as well just load everything into my namespace.
One solution to this problem is to use hygienic macros. The "bar" that the macro expands into would then be a syntax object that remembers where it came from instead of being a plain symbol. Should we be able to use hygienic macros in Arc if we want to? Sure. But I'd also prefer not to have to deal with hygienic macros just to be able to use modules.
Instead of having our foo macro expand into the bar symbol, we could instead have it expand into the literal bar function. Since function values aren't considered literals by the Arc compiler like numbers or strings are, the function value needs to be quoted:
We can choose to have the Arc compiler treat function values as literals if we want to:
and now we don't need to quote the function value:
Now we can see the tradeoff involved in choosing whether to implement my macro as a hygienic macro or not. If I had implemented foo as a hygienic macro, I would have already had to have gone to the trouble of specifying whether bar was supposed to refer to my environment or the caller's environment; but then I could have moved it into my module unchanged. In the non-hygienic macro symbols always refer to the caller's environment, so if I want it to refer to something in my module environment instead I have to explicitly say so with a comma.
The final piece of the puzzle is what to do if bar is also a macro. I can have my macro foo expand into the literal macro value bar by using a comma in the same way, but the Arc compiler checks if something is a macro by looking at a symbol and seeing if the symbol is a global variable that has a macro value. Having it also check for macro values is easy to add:
Now I can use bar as a helper macro in a macro foo, and I can import foo without having to also import bar.