Build a small version of what real module systems do.
What to build
define(name, deps, factory) and load(name), which load each module's dependencies first, and run each module only once.
Requirements
definestores a module: its name, the names it depends on, and its factory function.load(name)returns the module's value, and works out the value only the first time.- To work it out, load the dependencies in order, print
running NAME, and call the factory with the dependency values. - If a module is not defined, throw an Error with the message
unknown module NAME. - If a module is reached again while it is still being loaded, throw an Error with the message
circular dependency at NAME. - A failed load must not stop later loads from working.
Input
Several lines: define NAME DEP1 DEP2 ... (with zero or more dependencies) or load NAME.
Output
The running lines, then for each load, the value or error: MESSAGE.
Constraints
There are from 1 to 100 lines. Names are lower-case letters. The starter builds each module's value for you.
Example
Input:
define a b
define b
load a
load a
Output:
running b
running a
a(b())
a(b())
Why it works
Storing each finished value is what makes a module run only once, even when many others depend on it. The set of names being loaded catches cycles, and finally keeps that set accurate even after a failure.
Examples
define a b define b load a load a
running b running a a(b()) a(b())
load x
error: unknown module x
Hints
Hint 1
Keep the definitions in one Map, the finished values in another, and a Set of names being loaded.
Hint 2
load(name) returns a cached value at once. Otherwise it loads the dependencies, then runs the factory.
Hint 3
Use try ... finally to take a name out of the loading set, even when loading it fails.
Solution
Show one correct solution (try it yourself first)
const modules = new Map();
const finished = new Map();
const loading = new Set();
function define(name, deps, factory) {
modules.set(name, { deps, factory });
}
function load(name) {
if (finished.has(name)) {
return finished.get(name);
}
const module = modules.get(name);
if (!module) {
throw new Error(`unknown module ${name}`);
}
if (loading.has(name)) {
throw new Error(`circular dependency at ${name}`);
}
loading.add(name);
try {
const values = module.deps.map((dep) => load(dep));
console.log(`running ${name}`);
const value = module.factory(...values);
finished.set(name, value);
return value;
} finally {
loading.delete(name);
}
}
let line = prompt();
while (line !== null) {
const [command, name, ...deps] = line.split(" ");
if (command === "define") {
define(name, deps, (...values) => `${name}(${values.join(",")})`);
} else {
try {
console.log(load(name));
} catch (err) {
console.log(`error: ${err.message}`);
}
}
line = prompt();
}