Showing posts with label systems. Show all posts
Showing posts with label systems. Show all posts

Saturday, April 18, 2009

PLT System Facilities (Software Components): Module name resolvers

Because of eval, one program's "compile time" is another program's run-time. Even though they are not first-class, this also applies to PLT Scheme modules, since they too can be dynamically compiled, loaded and linked. As I've described before, namespaces are the general construct for managing multiple instances of modules and maintaining their top-level bindings. But I still haven't described how dynamic invocation of modules happens.

Module loading is managed by the system's module name resolver. The primary responsibility of the module name resolver is to map distinct but equivalent references to modules into canonical forms. For example, it's possible to refer to the PLT Scheme string library with either of the two forms:
(lib "string.ss" "scheme")
scheme/string
The standard module name resolver translates these to an absolute path to the module's source file in the filesystem.

The other responsibilities of the standard module name resolver are:
  • to ensure that a module is only loaded once,
  • to make sure each module is loaded the first time it is needed, and
  • to check for and prevent cyclic loading dependencies between modules.
The module name resolver is a pretty low-level part of the PLT system. For the most part, programmers stick to using dynamic-require to interact with modules dynamically. This dynamically ensures a module is loaded and optionally selects one of its exports by name (by querying its namespace). But the module name resolver is directly accessible and can be dynamically invoked to ensure a module is loaded and resolve its name to canonical form. The module name resolver is even configurable, and can be swapped out with a custom resolver--though I have not seen this done in user code.

Monday, March 16, 2009

PLT System Facilities (Software Components): Namespaces

So far I've described PLT Scheme's first-class component systems, as well as the second-class, static module system. Of course, Scheme is a reflective language. Where there's eval, there's not just one "compile time." Code can be compiled and evaluated at any time during the execution of a PLT Scheme virtual environment.

Moreover, considering that one of the most important applications written in PLT Scheme--DrScheme--is an IDE, it's clearly important to be able to load and reload a single module multiple times. Users have to be able to edit a module and re-evaluate it without restarting the Scheme image. (In fact, it's even possible to run DrScheme from DrScheme, so it must even be possible to have multiple instances of the same module running concurrently!)

Every instance of a module in a running PLT Scheme environment has associated with it a namespace. Namespaces serve two somewhat orthogonal purposes. The first is to store top-level bindings. A module's namespace contains the mapping of its global definitions (some of which may be made available to other modules via provide). There is also a namespace associated with the REPL, which can be dynamically modified; in DrScheme, that namespace is typically just the one associated with the current running instance of the module being edited. In a sense, this role of namespaces is to mediate between the static, segmented architecture of modules and the dynamic, global nature of the Scheme top-level. This is an interesting tension that comes in whenever designing a lexically scoped language with dynamic code evaluation such as with eval or a REPL.

The second purpose of namespaces is to maintain an internal module registry, which keeps track of running instances of modules. Every namespace has its own registry, but registries may share instances. This is how it's possible both to create multiple, isolated instances of modules and to share module instances and their associated state.

Thursday, February 19, 2009

PLT System Facilities (Software Components): Modules

Lexical scope in general and lambda in particular go a long way towards supporting modular, separate development. As I mentioned before, PLT Scheme builds a number of first-class, module-like component systems on top of lambda. But it also contains a more primitive module system in which modules are not first-class. This serves a number of purposes.

First of all, static modules provide systematic support for packaging, compiling, and deploying code. First-class modules are flexible and expressive, but they don't have anything to say about compilation and deployment. Somewhere along the line there has to be a notion of what the compiler takes as input, and when you have separate development, you need separate deployment and ideally separate compilation.

Another critical purpose of static modules is the ability to modularize static entities in a language. In ML, for example, modules can import and export types. Scheme is of course more [ed: dynamic] than ML, but it still has its own crucial compile-time abstractions: macros. With dynamic modules, there's no straightforward way to import and export static entities like macros. A secondary benefit of static modules is that you can import all the bindings from another module at once without having to spell them all out; this is admittedly less important but still very convenient.

Finally, PLT Scheme was designed to support multiple languages. For pedagogical purposes, this has allowed them to design multiple, concentric subsets of the language tailored to the How to Design Programs curriculum. This has also facilitated language research by making it easy to design and implement new languages (by macro-compiling them to Scheme) and to research language interactions in a multi-language environment. The relevant piece of the module system is a single hook at the beginning of a module definition: the grammar of a module is an S-expression containing the symbol module, a symbol naming the module, and an S-expression indicating the language of the body:
(module foo scheme body ...)
Typically the language chosen is the special built-in language scheme, as above, or the somewhat leaner scheme/base. But the language position works by simply importing another module that implements the required macros for compiling the body. In place of scheme, you can put in a module path for any module installed on the system.

It's also possible to specify a custom reader for a PLT language so it doesn't even have to be restricted to an S-expression syntax. The initial reader allows you to specify a language with the special shebang-like #lang syntax:
#lang scheme/base
body ...
From that point on, the language's reader has access to the input stream to parse it any way it likes.

Anyone can implement a language module, which means people have developed PLT implementations of Algol, Java, ML, and JavaScript, to name a few.

Update: Added in the missing word "dynamic" above. Also, this is a better link for module paths.

Also, Sam is right in the comments: the scheme language really isn't special or built-in in any significant way. It's simply another module provided in the standard PLT collections.

Wednesday, February 18, 2009

PLT System Facilities (Software Components): Lexical scope

Like any programming system, PLT Scheme involves multiple people and a lot of code. So managing software components is a primary concern. And of course, as a Scheme, PLT uses lexical scope as the linguistic linchpin for managing software. The importance of lexical scope in a component architecture is that it allows code to be shared and mixed while guaranteeing the internal integrity of components.

With the power of macros, PLT has developed a number of software component abstractions on top of little lambda. One of these is a single-inheritance, class- and interface-based OOP system. Classes are first-class values, which means it's easy to implement mixins (i.e., classes parameterized over their superclass) as functions that return classes. More recently, they've introduced traits, which are like fragments of classes that can be more freely and flexibly combined than mixins. Also built with macros is the unit system, which is a first-class, parameterized, recursive module system.

All of these abstractions admit modular and separate development because they protect the internal integrity of their local bindings and definitions. You can hand out a component into an untrusted context and know that it won't be able to modify or even inspect the component's internals. And you can reason locally about your code knowing that no context can change its behavior based on these internals.

Tuesday, February 17, 2009

The PLT Scheme Operating System

Back in the day, programming languages were considered a systems concern. These days a lot of PL research is done in a vacuum, with abstract models and stand-alone prototypes. But programming languages rise and fall by their applications, and the deployment of a language always involves interesting and non-trivial systems challenges.

The PLT group has done a ton of work in this area. A decade ago, Matthew, Robby, Shriram and Matthias wrote an ICFP paper on Programming Languages as Operating Systems, sketching a few of the highlights of the PLT virtual machine. They focused on MrEd, the GUI engine. But taking a step back, there are many more systems-y facilities in PLT Scheme than just the UI infrastructure. And of course, that paper is a decade old and beginning to show its age.

I'm planning to write a series of posts on some of the myriad systems facilities in PLT Scheme and how they fit together.

Friday, January 30, 2009

Quiz answer

Reader Mitch (not my advisor, apparently) comes closest to the answer to my quiz question:
Some kind of alignment thing? I can't think of how code would be affected by that, though.
As it happens, changing the code was affecting the particular size of the activation record allocated by the GCC-generated code. Somehow the extra code ended up with a slightly smaller activation record. And then it turned out that the slight difference (something like 8 or 16 bytes) was pushing the activation record over the edge of a page boundary. Boom!

It's still a mystery to me exactly how throwing in an asm("noop\n") can affect the size of the activation record. Such are the arcane mysteries of GCC code generation. I had to catch my flight home before Dave had worked out all the details but by now I bet he has an even better understanding of it. It was fun and inspiring to watch Dave's detective skills in action.

Quiz question

Here's a fun puzzle I watched Dave Mandelin track down yesterday. Why would adding a guaranteed no-op such as
if (FALSE) { printf("can't happen\n"); }
or even
asm("noop\n");
to the body of a procedure cause a program to run measurably and consistently faster? I'll post the answer later today. (Or as much as I understand, anyway.)