Step 5: Multi-File Projects¶
In this step, you'll learn to split your project across multiple files
using import declarations.
Why Multiple Files?¶
As projects grow, it helps to separate concerns:
- Constants in one file (shared across the project)
- Parameters in another (easy to find and tune)
- Main calculations in the entry point
Files Are Packages¶
Every .gcl file in Graphcal is a package. Without a
graphcal.toml manifest, the file is a virtual package — a
standalone Graphcal script. The package contains exactly one module:
the file itself. You can self-reference its top-level decls from
inline DAGs (e.g. import dynamics::{type T}; from inside dynamics.gcl),
but you cannot import a sibling file. The first multi-file step
in any Graphcal project is to add a manifest.
In other words: virtual = one file. The moment you want a second
file, you add a graphcal.toml and graduate to a real package. The
rest of this step walks through that promotion end to end.
Project Structure¶
A multi-file project always has a graphcal.toml manifest at the
root and source files arranged under the package's source directory:
rocket_project/
graphcal.toml # [package] name = "rocket_project"
src/
rocket_project/
constants.gcl
params.gcl
main.gcl
constants.gcl¶
params.gcl¶
main.gcl¶
import rocket_project.constants::{g0};
include rocket_project.params()::{dry_mass, fuel_mass, isp};
node v_exhaust: Velocity = @isp * @g0;
node mass_ratio: Dimensionless = (@dry_mass + @fuel_mass) / @dry_mass;
node delta_v: Velocity = @v_exhaust * ln(@mass_ratio);
Try the Multi-File Project Locally¶
The standalone playground supports only one .gcl file. Use the CLI for this multi-file lesson; it is not flattened into a different browser example.
Read the entry source, constants, parameters, and package manifest. Arrange them as shown above and run the command below.
Expected output includes all seven projected values from g0 through delta_v.
The path before ::{...} is absolute from the package root. The
first segment is the package name (from graphcal.toml); subsequent
segments walk the directory tree under source_dir.
Note params uses include, not import: params.gcl exposes
params (runtime values), and runtime values cross file boundaries
only through DAG instantiation. import brings compile-time names
only.
Running a Multi-File Project¶
Point graphcal eval at the entry file:
$ graphcal eval rocket_project/src/rocket_project/main.gcl
g0 = 9.80665 m/s^2
dry_mass = 1200 kg
fuel_mass = 2800 kg
isp = 320 s
v_exhaust = 3138.128 m/s
mass_ratio = 3.333333
delta_v = 3778.221 m/s
Graphcal resolves each import against the package tree.
The import Statement¶
There are three forms; pick the one that matches what you want to bring into scope:
import rocket_project.constants; // brings module `constants`
import rocket_project.constants as c; // brings module under alias `c`
import rocket_project.constants::{g0, g_mars}; // brings only `g0` and `g_mars`
The brace form is the most common in practice — it makes every imported name explicit.
Import Aliasing¶
If two files export the same name, rename one or both with as:
import rocket_project.file_a::{velocity as velocity_a};
import rocket_project.file_b::{velocity as velocity_b};
You can also alias a whole module:
What Gets Imported¶
import brings only compile-time names. Selective dimensions, units,
types, and indexes use the explicit dim, unit, type, and index
markers; bare items select terms such as constants, DAGs, assertions, and
constructors. To use a runtime value (like a param or non-const node)
from another file, include the producing DAG instead of importing the value (see
Multi-File Projects).
| Declaration kind | How to import | How to reference |
|---|---|---|
const node |
import package.file::{name} |
@name |
dim |
import package.file::{dim DimName} |
DimName |
unit |
import package.file::{unit unit_name} |
unit_name |
type |
import package.file::{type TypeName} |
TypeName |
index |
import package.file::{index IndexName} |
IndexName |
dag |
import package.file::{dag_name} |
include-d, or called as @dag_name(...)::out |
assert |
import package.file::{assert_name} |
#[assumes(assert_name)] |
When a Single File Suffices¶
If your whole calculation fits in one file, you don't need a manifest
at all. A standalone rocket.gcl script behaves like a virtual
package — its only externally addressable name is its own stem.
References from inline DAGs back to top-level decls use that
self-reference path:
// rocket.gcl (standalone script, no graphcal.toml)
type OrbitType { OrbitType(sma: Length, ecc: Dimensionless) }
dag analyze {
import rocket::{type OrbitType}; // file's own name
param o: OrbitType;
// ...
}
The moment you split into a second file, add a graphcal.toml at
the project root and arrange the files under <source_dir>/<pkg>/
as shown above. Sibling-file imports are rejected with a clear
error from any file that is not itself inside the package namespace —
including a file sitting next to a graphcal.toml but outside its
<source_dir>/<pkg>/ directory.
Circular Import Detection¶
Graphcal detects circular imports at compile time. In a real package
with two modules <pkg>.a and <pkg>.b:
// src/<pkg>/a.gcl
import <pkg>.b::{x};
// src/<pkg>/b.gcl
import <pkg>.a::{y}; // ERROR: circular import
Assertions Belong to Explicit Instances¶
An import loads compile-time names without evaluating the module, so it does
not run assertions. Every explicit include instance runs its own assertions
with that instance's bindings. Select an assertion in the include braces when
you need its name in #[assumes(...)]. See
Assertions for
details.
What You Learned¶
- Every
.gclfile is a package — virtual (single-file standalone script) or real (manifest-backed multi-file project). - A virtual package has exactly one file. Multi-file projects always
have a
graphcal.toml. - The three
importforms — bare, aliased, and brace list — bring exactly the names you write into scope. importis for compile-time names; runtime values cross file boundaries viainclude.- Circular imports are detected automatically; assertions run for explicit include instances.
Next Step¶
In Step 6, you'll work with indexed collections for multi-element calculations.