Reusable include files should usually define classes and functions, not create a large set of root variables in the caller session.
A good pattern is to put reusable workflow code in classes, include the file with a namespace alias, and create only the objects that the caller actually needs. This keeps include files easier to reuse and also prevents accidental session pollution.
This kind of include is convenient at first, but it pollutes the caller session:
// probe_setup.expr safe_z = 5; probe_feed = 100; probe_count = 0; function SetProbeFeed(feed) { probe_feed = feed; } function ProbeDepth() { probe_count += 1; return safe_z; }
After this file is included, `safe_z`, `probe_feed`, `probe_count`, `SetProbeFeed`, and `ProbeDepth` are all root names in the active session.
That creates several risks:
A class keeps related state together. The session stores the class definition, and each object instance stores its own fields.
// probe_lib.expr class ProbeJob() { safe_z = 5; probe_feed = 100; probe_count = 0; function SetSafeZ(value) { safe_z = value; } function SetFeed(feed) { probe_feed = feed; } function ProbeDepth() { probe_count += 1; return safe_z; } }
The caller creates an object and uses methods on that object:
include "probe_lib.expr" as Probe job = Probe::ProbeJob(); job.SetFeed(120); z = job.ProbeDepth(); print('safe z ', z); print('probe count ', job.probe_count);
Only `job` and `z` are caller root variables. The probe settings are fields inside `job`.
Include library files with an alias when they define reusable classes or functions.
include "probe_lib.expr" as Probe include "atc_lib.expr" as ATC probe_job = Probe::ProbeJob(); atc_job = ATC::ToolChangeJob();
The alias makes ownership clear and prevents library names from mixing with caller names.
Without an alias, names from the include are added directly to the current namespace. That is sometimes useful for small scripts, but it is not the best default for reusable libraries.
Reusable include files should usually define classes and functions only.
Avoid this in library files:
// avoid in reusable library files session.create('probe_job'); session.join('probe_job'); probe_job = ProbeJob();
A library that changes sessions changes the caller's execution context. That makes the include harder to reuse safely.
Prefer this:
// reusable library file class ProbeJob() { safe_z = 5; function GetSafeZ() { return safe_z; } }
Then let the caller decide where the object belongs:
include "probe_lib.expr" as Probe probe_job = Probe::ProbeJob(); print(probe_job.GetSafeZ());
When state must persist in a session, store one object instead of many separate root variables.
Better:
include "probe_lib.expr" as Probe probe = Probe::ProbeJob(); probe.SetSafeZ(5); probe.SetFeed(100);
Avoid spreading one workflow across many root variables:
probe_safe_z = 5; probe_feed = 100; probe_count = 0; probe_last_result = none(); probe_last_error = none();
The object form is easier to inspect, pass to functions, replace, and remove.
probe = none();
Classes make it natural to keep two jobs independent in the same session.
include "probe_lib.expr" as Probe left = Probe::ProbeJob(); right = Probe::ProbeJob(); left.SetFeed(80); right.SetFeed(120); print(left.ProbeDepth()); print(right.ProbeDepth());
Each object has its own `probe_feed`, `probe_count`, and other fields.
Objects are also useful for callback state in dialogs, timers, serial handlers, and other host-integrated workflows.
class DialogState() { accepted = false; value = none(); function OnResult(result) { accepted = result.ok; value = result; } } state = DialogState(); dlg = msg_ok_cancel(); dlg.message('Continue?'); dlg.on_result(state.OnResult); dlg.show();
The callback updates `state` instead of scattering callback variables across the session.
Root variables are not bad. They are useful for:
The goal is not to avoid root variables completely. The goal is to avoid accidental session state.
For reusable scripts:
Before writing a reusable include file, ask: