I’m in the process of hammering out a concrete design for Awelon, and my design efforts have taken me to some interesting places. My current application model looks like this:
- An Awelon application consists of a set of anonymous modules.
- Each module will directly manipulate the link-time environment.
- When the link-time environment stabilizes, the runtime behavior can be extracted.
Modules collaboratively specify a runtime behavior. Each module is fully responsible for its own link-time behavior. This contrasts with more traditional approaches to modularity, where modules are passive objects to be integrated by some unspecified third party. The link environment provides the sandbox for the modules to work. It may provide stateful resources, but it’s ultimately confined.
This design would be problematic for a procedural programming model due to non-deterministic ordering issues. But Awelon is a reactive demand programming language, and thus has synchronous concurrency. All the effects are applied at the same logical time.
I imagine that most modules will not directly contribute to runtime behaviors. Instead, many modules will contribute knowledge or components to shared locations in the link environment, e.g. parts of a world map or dialog tree, components of a UI specification, pages for the web service, constraints, defaults, overrides. Developers may then use link-time metaprogramming to construct the runtime behavior.
The link environment is cleanly partitioned from the runtime environment using the type system, and may be discarded after use. Logically, the environment is still present (otherwise the extracted dynamic behavior would expire). But since the environment is constant, and the extracted behavior won’t change (without a corresponding change in code), the environment can be discarded. (Though we might keep it around for live programming.)
This design supports a style I call stone soup programming. The modules are the ingredients. Developers can easily toss modules into a project to extend behavior or contribute flavor. The modules will link themselves under their own power, without any built-in constraints model.
As a disadvantage: it’s easy to ruin a soup.
Direct composition of the ingredients (e.g. clam beef stew chowder) doesn’t always work out. It’s an experiment. To address this, Awelon enables decomposition of an application into multiple component apps, each of which build in a separate link environment then are applied or composed in the parent. Usefully, the integrated app is subject to further optimization.
Given high levels of link-time metaprogramming, isolating a problem can become a hassle. This issue is partially addressed by a good development environment (e.g. ability to browse the link environment) and partially by discipline (one responsibility, clear documentation). Decomposition might help.
Individually, each module goes through a similar process to parse and extract link-time behaviors. (By handling each module independently, we ensure that at least the parse is independent of the application, and we support caching.)
Overall, this system is simpler, more consistent, more transparent, and more flexible than my earlier efforts. Some of my earlier approaches had constraint solvers built into the linkers, effectively forcing developers to learn another programming model. Here, I might provide constraint solvers in the link environment, but their use is voluntary.
The main concern I have is that linking is now a first-class concern, and developers might not be used to that sort of staged reasoning. But I believe this can be addressed via user-defined syntax, e.g. creating languages for cases where linking becomes boiler-plate.