I have just about finished putting together a basic module system for Radian, which will allow programs to span multiple source files. Designing this system was more difficult than I had anticipated; the module system says a lot about developer workflow, so I had to think pretty far ahead about the things I expect people to do with this language and the ways they will likely want to do it.
The module system came about because I want to start building a standard utility library, and in order to do that the compiler needs to link a program together from multiple source files. I want to have some semi-automatic mechanism for linking against the standard library, but it seemed more sensible to build that on top of a generic linking mechanism than to start with the special case and generalize it.
Lessons learned from experiences with other languages:
- Modules should not be able to define global identifiers. A client program can always import a qualified symbol into its unqualified namespace, but there’s no way to prevent imported global identifiers from conflicting with each other.
- Source files should import dependencies explicitly. Interpretation of a source file should not depend on any external context, like a project file, an environment variable, or the contents of some shared directory.
- Support modules should be initialized and finalized explicitly, in the main program, so that the programmer can control the dependency order.
- The structure of the program should be visible in the filesystem. Don’t trip people up by introducing a parallel-but-different structural hierarchy.
- Makefiles are evil. The language must allow the programmer to describe the program in such a way that the compiler can identify all of its parts and build a finished executable in one step.
The system I’ve built works like this. You invoke Radian on a program file; this is equivalent to the “main” function in C. This program file may import module files, which may in turn import other module files, using the import statement:
This statement declares the name “foo”, representing the contents of the file “foo.radian”, which the compiler expects to find in the same directory as the client file. Imports are simply placeholders, to be resolved at link time, so circular references are not a problem.
The Radian compiler treats the contents of an imported module file as the body of an object declaration. The top-level functions and other declarations in the file become the members of the imported object. A source file cannot simultaneously be a program and a module, since only a program file gets the implicit “io” variable allowing interaction with the rest of the system.
That’s all I’ve built for now. As far as the standard library goes, I think I’ll throw an implicit “radian” import into the top-level namespace of every file. All of the standard utilities will be members of this namespace – much like “std” in C++. This will allow me to extend the standard library in future versions without introducing name conflicts. To make this more convenient, I want to extend the import statement, like Python but in a more sensible order:
import stack from radian
This would define a new item named “stack”, equal to “radian.stack”; you could extend the “from” expression arbitrarily to handle deeper nesting. You wouldn’t have to have imported the “from” identifier on its own; you could import only the item you wanted and leave the rest of the package unimported.
Proceeding onward, I expect that you’ll be able to treat subdirectories as modules – if you had a subdirectory “foo” next to your program file, containing a module file named “bar.radian”, you could import it like this:
# get access to the module file only, resulting in 'bar'
import bar from foo
# import the whole directory, resulting in 'foo.bar'
I’ll need to design a package system as well, but that is still some distance ahead. It’ll probably look something like python’s packages, which remind me of Mac OS X bundles. I am fairly well convinced that the convenience of a standard central package directory is outweighed by the configuration hassles and dependency tracking issues, so I think I will require packages to be included in the project folder. If people want to keep a central repository of useful libraries, or several such repositories, they can always make a softlink/alias into the project folder.