Skip to main content
Language v0.2.0 · Preview

Typed program results and symbolic errors

Negotiate typed Result values for main, exact ok payloads, bounded symbolic program errors, and explicit separation from engine failures.

Separate program results from engine failures

NM-RFC-0007 gives main a bounded Result<T, Error> contract without changing stable N/M 0.1 return behavior. Every surface must negotiate the feature explicitly.

NM-RFC-0007experimental0.2.0-runtime-experimental
64
error-code bytes
1
final returns
2
outcome kinds

One declared result type

Bit, Int, Float, and fixed bounded arrays are accepted. ok(value) must match T exactly; implicit numeric and array conversion is forbidden.

fn main() -> Result<Array<Int, 4>, Error>

Bounded program errors

error("UPPER_SNAKE_CODE") represents a deliberate user-program outcome. Codes are symbolic, bounded, and never contain stack traces or provider details.

return error("NO_VALID_SAMPLE");

Two independent outcomes

SimulationResult.success reports parser, compiler, transport, and simulator execution. programResult.status reports the program's ok/error outcome, so a program error keeps engine success true.

success: true · programResult.status: error

Deterministic control flow

The base 0.2 contract requires exactly one final top-level return and rejects nested returns. Enabling classicalFunctions and typedClassicalValues as well adds the measured-branch and helper Result control flow described below; every reachable path must return the declared type.

exactly-one-final-top-level-return

Typed measurement result

module typed_result; fn main() -> Result<Int, Error> {  let q: QReg<1> = qreg[1];  H(q[0]);  let measured: Bit = measure(q[0]);  let encoded: Int = bit_to_int(measured);  return ok(encoded);}

Runtime outcome contract

{
  "success": true,
  "programResult": {
    "contractVersion": "0.2.0-runtime-experimental",
    "status": "error",
    "error": { "code": "NO_VALID_SAMPLE" },
    "line": 8
  }
}

Explicit negotiation on every surface

parseNMCode(source, { experimental: { functionResults: true, typedClassicalValues: true } })
nm run main.nm --experimental-function-results --experimental-typed-classical-values --json
initializationOptions.nm.experimental.functionResults = true
nm.experimental.functionResults = true
Playground: on in new sessions; saved settings are preserved

Stable fail-closed diagnostics

NM-PARSE-066NM-PARSE-067NM-TYPE-025NM-TYPE-026NM-RUNTIME-033

Named classical functions and parameters have a separate experimental contract (NM-RFC-0035), described below. Recursion, branch-sensitive returns, generic error types, free-form messages, exceptions, catch/finally, and result propagation operators remain outside this result contract.

Classical functions — experimental

Reuse calculations with scalar, fixed-array, Matrix/Complex and record/enum parameters and return types; the sections below describe each supported type and its bounds. The checked_observations wrapper returns a typed Result. The example returns Float: 1; change enabled to false to obtain Float: 0, or change its 0.5 argument to -0.5 to return the NEGATIVE_THETA error before the gate executes.

Open the example in Playground and run it. It enables classical functions, typed values and typed results for this session. You can change these options in Tools; saved work and share links retain them.

Try classical functions

average(samples) → Float: 1

module classical_runtime; fn double(x: Int) -> Int {  return x * 2;} fn square(x: Float) -> Float {  return x * x;} fn energy(theta: Float, weight: Float) -> Float {  let scaled: Float = weight * square(theta);  return scaled + 0.5;} fn observations(theta: Float, enabled: Bool) -> Array<Float, 2> {  if (!enabled) {    return [0.0, 0.0];  }  return [energy(theta, int_to_float(double(1))), square(1.0)];} fn average(values: Array<Float, 2>) -> Float {  return mean(values);} fn checked_observations(theta: Float, enabled: Bool) -> Result<Array<Float, 2>, Error> {  if (theta < 0.0) {    return error("NEGATIVE_THETA");  }  return ok(observations(theta, enabled));} fn main() -> Result<Float, Error> {  let q = qreg[1];  let enabled: Bool = true;  let samples: Array<Float, 2> = checked_observations(0.5, enabled)?;  let result: Float = average(samples);  Ry(q[0], average(samples));  return ok(result);}

Functions can accept, store and return Array<Int, N> or Array<Float, N> with 1–256 elements. Element type and length must match exactly. In helpers and main, read elements with an Int expression index or compute mean(values). Runtime bounds are checked. Helper arrays and ordinary let arrays remain immutable. Main supports explicit let mut Int/Float arrays under the array mutation contract below; nested arrays remain unsupported. Dynamic main indexing requires classicalFunctions and typedClassicalValues.

Bool is separate from Int and measurement Bit: use true/false, !, comparisons and short-circuit &&/||. Helpers support if (condition) { ... } else { ... }; else is optional when a later return covers the remaining path. Every path must return the declared type. Only the selected branch executes. Branch locals stay private; they cannot shadow an outer binding, while sibling branches may reuse names. Main supports top-level let enabled: Bool and Result<Bool, Error>. Bool arrays are unsupported.

The base example returns Result<T, Error>, where T can be Int, Float, Bool, Array<Int, N> or Array<Float, N> with 1–256 elements. Helpers accept base values and Result parameters. Helpers and main can store Result locals and handle exhaustive ok/error match arms. The sections below extend these base examples with typed errors and compound Result payloads. Main additionally supports Bit payloads without implicit numeric conversion. Return ok(value), error("CODE") or its err("CODE") alias. Error codes match [A-Z][A-Z0-9_]{0,63}. Postfix ? on a Result helper call unwraps success or returns its error from the enclosing function; that enclosing helper or main must declare a Result return. For example, main can use a typed local initialized by helper(...)?, return helper(...), or return ok(helper(...)?). Arguments and selected branches retain source evaluation order; typed errors are distinct from runtime failures. Enable functionResults explicitly alongside classicalFunctions and typedClassicalValues. Numeric helper(...)? values can supply supported gate angles at runtime; documented typed Result recovery paths support analytic differentiation. Native Error Result helper derivatives and parameter-dependent discrete paths are rejected. Quantum source carriers support statically successful propagation; error-producing or unavailable propagation cannot be projected into a numeric circuit. Keep the required sidecar.

Use numeric helper results directly in rotation-gate angles, such as Ry(q[0], average(samples)). Arguments and angles are evaluated once, left to right, immediately before the gate. Earlier typed values, constants, parameters and explicitly converted measurement results can supply inputs. U accepts three independently checked angles; Bool and array results cannot serve as angles. The example rotates by 1 radian when enabled, or 0 when disabled. Quantum exports freeze resolvable angles at export-time parameter values; measurement-dependent or failing angles require the N/M runtime and cannot be exported as a numeric circuit.

In the desktop editor, Ctrl+Space suggests local functions and values in scope; hover shows their types. Use Ctrl+Shift+Space for call signatures and F12 to go to a declaration. Functions, parameters and locals have distinct semantic colors; only immutable bindings carry readonly modifiers. Use Shift+F12 for references and F2 to rename a resolved function, parameter or local within this file. Refactoring includes helper and main block locals and requires complete, valid code; conflicting or capturing names are refused. Existing main and module param/const references retain their bindings.

Use the source debugger to inspect helper calls. Step in enters a call, Step over skips its nested calls, and Step out returns to the caller. The stack shows arguments, initialized locals and typed return/error values, including arrays and Result. Imported helpers display their own module location; they do not highlight a line in the main editor. Navigating recorded steps does not repeat effects. Editing the entry, a dependency or experimental options requires a fresh recording. A recorded runtime failure stays visible while you inspect its preceding steps; typed Result errors remain separate program outcomes.

Optimize can remove adjacent literal inverse quantum gate pairs in top-level main while preserving comments, helper bodies, calls and workspace sources. It does not cross classical statements, rewrite helper bodies, merge rotations or optimize lowered helper ASTs. The preview shows exact source. Apply is available only after structural checks and matching runtime results, visible classical values and complete complex quantum amplitudes for the same inputs and dependencies. Missing dense-state evidence, failed execution or skipped verification keeps the original source. Exact checks are bounded to 14 qubits; measurements require an explicit seed. Noise, training and other excluded runtime/resource-sensitive programs are not changed. Editing source, flags or dependencies invalidates the verified preview.

Use workspace.helpers to import public helpers from another local .nm module. Plain fn is public; public/private fn requires the module visibility option. A public function retains access to its own private helpers, while callers see only direct public imports. Source carriers include the required files; retain workspaceModules when restoring them. The VS Code extension resolves definitions, signatures and rename across files. Missing modules, cycles and ambiguous names are rejected.

Calls in main may appear in arithmetic inside a typed let assignment or ok(...) result, including helper(1 + helper(2)). Operators +, -, *, /, %, unary minus and parentheses preserve evaluation order. Int results can be converted explicitly with int_to_float(helper(2)), including inside another function argument. Numeric fixed arrays of 1–256 matching Int or Float elements may contain calls; mean(...) accepts an inline or stored array. Stored arrays support literal and runtime Int indices, including helper calls. See the compound-value guides below for supported nested types and size limits. Main loops and dynamic indices/targets follow the bounded runtime examples below. Main measurement branches and supported bounded blocks can call helpers; their existing control grammar still applies. Recursion remains unsupported. Workspace imports follow the separate contract below. Core can preserve the original source and export versioned JSON IR; the CLI command is nm export main.nm --to ir with all three experimental flags. QASM3 export is available with a mandatory source sidecar: use --to qasm3 --strict --json. QASM runs the quantum operations; classical results require the sidecar and N/M runtime. Qiskit/Cirq/PennyLane support a unitary circuit subset with a mandatory source bundle; use --to pennylane --json in the CLI. Measurement, reset, noise and quantum control flow are excluded from this framework slice; helper branches remain in the source bundle. Python does not execute classical results or errors. Source-preserving optimization uses the separate bounded contract below. Plain classical-function QASM2 export remains unavailable; unitary compute/apply has a separate validated QASM2 sidecar contract.

Train a numeric helper — experimental

This noiseless expectation-value example trains theta through square(theta). The adjoint gradient includes the classical chain rule: cost = cos(theta²), so dcost/dtheta = −2theta · sin(theta²). At theta = 0.8 the initial gradient is about −0.955513; minimizing the cost should lower its value. This is a simulator exercise, not evidence of quantum advantage.

Open this example and choose Run. The link enables classicalFunctions and typedClassicalValues explicitly for the session. @optimize runs 16 Adam updates; GD and momentum are also supported. Inspect the optimization result for cost history, final parameters and the reported adjoint method. Changing source, dependency files or flags cancels the old run and clears its result.

Local helper: main.nm

module helper_training;@optimize(method: "adam", steps: 16, learningRate: 0.1, objective: "minimize");param theta: Angle = 0.8; fn square(x: Float) -> Float {  return x * x;} fn main() {  let q = qreg[1];  Ry(q[0], square(theta));  let cost = expect Z(q[0]);  return cost;}

This slice supports numeric helper angles in noiseless expectation-value training. Native Error Result helpers, parameter-dependent Bool decisions, noisy training and unsupported gradient paths are rejected. Typed Result recovery helpers can use their documented numeric derivative paths. A Result return from main is separate from a Result-returning helper; it requires functionResults. Source-bound supervised training also supports pure numeric helpers and custom losses; see the custom loss example below. SPSA and optimizer comparison do not inherit this support. For ordinary helper expectation training, Core cost-landscape and Bloch-vector evaluations accept the same explicit options and dependency snapshot. These surfaces are unavailable for dataset-averaged custom loss.

Use the two-file transfer below to load the entry source and helpers.nm together. In Playground, review and load source and context, then Run. The transfer preserves classicalFunctions and typedClassicalValues and never runs automatically. Save both files from Workspace afterward. Plain fn is public by default; explicit public/private fn additionally needs moduleVisibility. Editing helpers.nm invalidates the previous result.

Imported helper: main.nm

module helper_training;@optimize(method: "adam", steps: 16, learningRate: 0.1, objective: "minimize");use workspace.helpers;param theta: Angle = 0.8; fn main() {  let q = qreg[1];  Ry(q[0], square(theta));  let cost = expect Z(q[0]);  return cost;}

Dependency: helpers.nm

module helpers;fn square(x: Float) -> Float {  return x * x;}

Save both files as a workspace project. Helper training is not yet supported by quantum QASM/framework export carriers; these exports refuse the program instead of emitting initial angles. Ordinary QASM training exports require successful final-parameter evidence. Source-only Quantum AI handoff, model JSON and training checkpoints do not yet carry these experimental options and helper dependencies. Their rejection does not mean the Playground training failed. Restore the project, enable the required options explicitly, and rerun; do not treat the exported entry source alone as a complete training artifact.

Train with your own loss (experimental)

This example performs two gradient updates on moons_2d. residual uses a two-iteration bounded loop and mutable Float accumulation to compute twice the squared error and the result retains its custom loss name. The first Float argument is a quantum expectation in [-1, 1]; the second is a constant -1 or +1 label. This is a simulator exercise, not evidence of quantum advantage.

Open in Playground and choose Run. The link enables classicalFunctions and typedClassicalValues without running automatically. Inspect the residual name, loss history, final parameters and separate validation/test losses. Test data does not select parameters. Run again after changing the source or options.

custom-training.nm

module custom_training;@dataset("moons_2d", split: "train");param theta: Angle = 0.37;fn residual(prediction: Float, label: Float) -> Float {  let mut total: Float = 0.0;  for i in 0..2 {    total = total + (prediction - label) * (prediction - label);  }  return total;}fn main() {  let q = qreg[2];  encode(sample_row(0), q, method: "reupload");  Ry(q[0], theta);  let cost = expect Z(q[0]);  train {    objective: minimize cost;    optimizer: gd(lr = 0.1);    steps: 2;    dataset: moons_2d;    loss: custom(residual);  }  return cost;}

The entry requires a non-generic function with two Float parameters and a Float return. Nested pure numeric helpers are supported. Analytic training supports helper loops with literal integer bounds within 0–256 (end-exclusive), mutable Int/Float local accumulation and checked Int indices independent of the differentiated input. Nested calls and loops share the 4096-step budget. Parameter-dependent discrete decisions/indices and runtime-dependent loop bounds remain unsupported. Typed Result recovery and bounded main-loop derivatives follow their separate documented contracts. Quantum effects, recursion and captured external state remain unsupported. Noiseless statevector training supports angle, reupload, iqp and amplitude encoding; amplitude must be the first operation on its register. Enable qmlTrainingControls too when adding validation cadence, early stopping or checkpoints.

Domain, derivative and budget errors stop training rather than substitute a zero derivative. Diagnostic locations are distinguished from function-entry context. Work counters report admitted computation, not hardware gate counts or a time guarantee. Custom loss does not produce a cost landscape or Bloch trajectory. QASM/framework, model JSON and checkpoint exports do not yet carry this custom loss contract; retain the source and helper files.

Float math and activations

Use trigonometric, exponential, logarithmic and activation functions for numeric calculations or rotation angles. Functions in this catalogue take one Float and return Float; convert Int explicitly with int_to_float(...). Sine/cosine also accept Angle. Float angles are radians; use degrees(...) to construct an explicit Angle from degrees.

  • sin(value: Float) -> Float — Sine of a Float angle in radians. Also accepts an Angle and returns a Float using its canonical radian quantity.
  • cos(value: Float) -> Float — Cosine of a Float angle in radians. Also accepts an Angle and returns a Float using its canonical radian quantity.
  • sqrt(value: Float) -> Float — Square root of a nonnegative Float.
  • exp(value: Float) -> Float — Natural exponential of a Float; rejects results above the Float bound.
  • log(value: Float) -> Float — Natural logarithm of a strictly positive Float.
  • log1p(value: Float) -> Float — Natural logarithm of 1 + value, accurate near zero; requires input greater than -1.
  • expm1(value: Float) -> Float — Natural exponential minus one, accurate near zero; rejects results above the Float bound.
  • tanh(value: Float) -> Float — Hyperbolic tangent of a Float, in the range [-1, 1].
  • sigmoid(value: Float) -> Float — Stable logistic sigmoid of a Float, in the range [0, 1].
  • abs(value: Float) -> Float — Absolute value of a Float; parameter-dependent zero has no analytic derivative.
  • floor(value: Float) -> Float — Rounds down to an integer-valued Float; analytic derivatives reject parameter-dependent integer inputs.
  • ceil(value: Float) -> Float — Rounds up to an integer-valued Float; analytic derivatives reject parameter-dependent integer inputs.
  • round(value: Float) -> Float — Nearest integer-valued Float, ties toward positive infinity; analytic derivatives reject parameter-dependent half-integers.

Vectors and numeric operations

  • min(Float, Float) -> Float — Minimum
  • max(Float, Float) -> Float — Maximum
  • clamp(Float, Float, Float) -> Float — Clamp to inclusive bounds
  • sum(Array<T, N>) -> T — Checked sum
  • dot(Array<T, N>, Array<T, N>) -> T — Inner product
  • norm(Array<Float, N>) -> Float — Euclidean norm
  • add(Array<T, N>, Array<T, N>) -> Array<T, N> — Elementwise addition
  • sub(Array<T, N>, Array<T, N>) -> Array<T, N> — Elementwise subtraction
  • scale(Array<T, N>, T) -> Array<T, N> — Scalar multiplication
  • variance(Array<Float, N>) -> Float — Population variance
  • stddev(Array<Float, N>) -> Float — Population standard deviation
  • argmin(Array<T, N>) -> Int — First minimum index
  • argmax(Array<T, N>) -> Int — First maximum index

T is Int or Float; N is a fixed array length from 1–256. Two vectors must have the same element type and length; the scale multiplier must match the element type. sum/dot preserve that type. min/max/clamp take Float only; clamp requires lower ≤ upper. norm/variance/stddev take Float arrays only. variance is population variance (divided by N); stddev is its square root. argmin/argmax return a zero-based Int index, choosing the first element on ties. In the example, bend uses dot to compute the same rotation angle; magnitude shows vector length and spread shows variance.

Analytic gradients reject parameter-dependent argmin/argmax, min/max ties, norm at zero, and zero stddev for arrays with multiple elements. clamp rejects parameter-dependent boundary contacts; equal constant bounds produce a constant with zero derivative. Single-element stddev has zero derivative. Constant operations are exempt from derivative restrictions, but numeric bounds and shape checks still apply. Vector operations consume the helper execution budget in proportion to their element count.

A typed main expression needs typedClassicalValues. Helper bodies and direct gate-angle calls also need classicalFunctions. The example enables both: scale = 2, wave ≈ 0.389418, and the Z expectation is ≈ 0.952382. Imported helpers may use sin/cos/sqrt too; no standard-library import is needed.

Open Float math example

Float / Array → Float

module float_math;param theta: Angle = 0.4; fn bend(x: Float) -> Float {  return dot([sin(x), cos(x)], [1.0, 0.25]);} fn main() {  let q = qreg[1];  let scale: Float = sqrt(int_to_float(4));  let wave: Float = sin(theta);  let probability: Float = sigmoid(theta);  let activation: Float = tanh(theta);  let features: Array<Float, 2> = [wave, activation];  let magnitude: Float = norm(features);  let spread: Float = variance(features);  let bounded: Float = clamp(probability, 0.0, 1.0);  Ry(q[0], bend(theta) / scale);  Rz(q[0], cos(theta));  let cost = expect Z(q[0]);  return cost;}

sqrt(-1.0) fails at runtime; it is not silently replaced by zero or NaN. sqrt(0.0) is valid during ordinary evaluation. This version does not support analytic gradients through parameter-dependent sqrt at zero; training/gradient explicitly rejects it. Parameter-independent sqrt(0.0) remains constant. Unselected short-circuit branches are not evaluated.

log requires positive input; log1p requires input greater than -1. exp and expm1 fail when their result exceeds the 10¹² Float bound. log1p/expm1 retain precision near zero. Sigmoid and tanh avoid overflow at large inputs; floating-point rounding may produce their endpoints. The example yields probability ≈ 0.598688 and activation ≈ 0.379949.

abs, floor, ceil and round return Float. round resolves ties toward positive infinity: round(-1.5) = -1.0. Analytic gradients reject parameter-dependent abs at zero, floor/ceil at integers, and round at half-integers. Elsewhere rounding has zero derivative, so a parameter connected only through rounding receives no gradient update. Parameter-independent constants are exempt from these derivative restrictions.

Names in the unary Float catalogue are reserved; do not use them for custom helpers. In the numeric catalogue below, a same-named user function, including an imported helper, takes precedence over the builtin.

Make angle units explicit

Construct a 30-degree Angle with degrees(30.0). Angle stores radians; degrees and radians do not guess units. Sine, cosine and supported rotation gates consume this quantity.

  • radians(value: Float) -> Angle — Construct an Angle from a Float measured in radians. No implicit wrapping.
  • degrees(value: Float) -> Angle — Construct an Angle from a Float measured in degrees. Values and derivatives are scaled by pi/180.
  • to_radians(value: Angle) -> Float — Read the canonical radian quantity as a Float.
  • to_degrees(value: Angle) -> Float — Read the angle in degrees as a Float. Values and derivatives are scaled by 180/pi.

Degrees, radians and derivatives

Open in Playground

Degrees, radians and derivatives

module angle_units;param theta:Angle=30.0;fn heading(value:Float)->Angle{return degrees(value);}fn main(){ let q=qreg[1]; let angle:Angle=heading(theta); let radians_value:Float=to_radians(angle); let degrees_value:Float=to_degrees(angle); Ry(q[0],radians(radians_value)); let cost=expect Z(q[0]); let slope=gradient cost wrt theta; return cost;}

Carry Angle through a record and Result

Open in Playground

Carry Angle through a record and Result

module angle_result;param theta:Angle=30.0;record Heading{angle:Angle}enum Problem{Unavailable}fn checked(value:Float)->Result<Heading,Problem>{ return ok(Heading{angle:degrees(value)});}fn main()->Result<Angle,Problem>{ let q=qreg[1]; let item:Heading=checked(theta)?; Ry(q[0],item.angle); let cost=expect Z(q[0]); let slope=gradient cost wrt theta; return ok(item.angle);}

Return an Angle with a builtin error code

Open in Playground

Return an Angle with a builtin error code

module angle_builtin_result;fn checked(value:Float,available:Bool)->Result<Angle,Error>{ if(!available){return error("UNAVAILABLE");} return ok(degrees(value));}fn main()->Result<Angle,Error>{ let q=qreg[1]; let attempt:Result<Angle,Error>=checked(30.0,true); match(attempt){  ok(angle)=>{Ry(q[0],angle);}  error=>{X(q[0]);} } let cost=expect Z(q[0]); return attempt;}

The first example produces degrees_value = 30, radians_value ≈ 0.523599, cost ≈ 0.866025 and slope ≈ -0.008727. The derivative is with respect to the degree input: -sin(π/6) × π/180. Training preserves this conversion factor too.

The existing param theta:Angle declaration uses the numeric parameter bridge; heading(theta) explicitly interprets that number as degrees in this example. Legacy numeric angles passed directly to gates without degrees are radians.

Change true to false in the last example to return UNAVAILABLE and apply X in the error arm. With true, it applies the 30-degree rotation. The existing analytic differentiation/training restriction for builtin Error helpers remains; this example demonstrates execution and error handling only.

Angle + Angle and Angle - Angle are valid. Scale by a Float, or divide Angle / Angle to obtain a dimensionless Float ratio. Angle + Float and Angle * Angle are errors; angles are not automatically wrapped into 0–360 degrees.

The second example uses Result with a custom enum error type; the last uses builtin Error. Array<Angle, N> is not supported yet. Values must be finite and bounded; invalid operations are not disguised as zero results. Analytic differentiation through parameter-dependent decisions is unsupported.

Compute/apply: reverse the preparation

The compute block prepares the state, the apply block performs the operation, then the inverse preparation gates run. Preparation arguments are evaluated once and reused during reversal. The option is on in new sessions and can be disabled in Tools. Saved settings are preserved; the links below carry this option explicitly.

Implement X with HZH

The H, Z, H sequence maps |0⟩ to |1⟩. Run it, then open the State panel: the probability of |1⟩ is 1.

Open in Playground

Implement X with HZH

module compute_demo;fn main(){ let q=qreg[1]; compute {H(q[0]);} apply {Z(q[0]);}}

Differentiate the preparation angle

At theta = 0.3, cost = cos(2 × theta²) ≈ 0.983844 and slope = -4 × theta × sin(2 × theta²) ≈ -0.214835. Change theta and run again; the derivative includes both preparation and reversal.

Open in Playground

Differentiate the preparation angle

module compute_gradient;param theta:Angle=0.3;fn main(){ let q=qreg[1]; compute {Ry(q[0],theta*theta);} apply {Z(q[0]);} let cost=expect Z(q[0]); let slope=gradient cost wrt theta; return slope;}

Reversal does not guarantee zero ancillas

Starting from |00⟩, X in apply changes the control qubit. The inverse CNOT then changes the ancilla too, producing |11⟩. Reversing preparation does not guarantee that ancillas end in |0⟩.

Open in Playground

Reversal does not guarantee zero ancillas

module ancilla_example;fn main(){ let q=qreg[2]; compute {CNOT(q[0],q[1]);} apply {X(q[0]);}}

Compute/apply bodies must be unitary: measurement, reset and noise are disallowed inside them. Analytic derivatives require noiseless exact expectations. QASM/framework output is a fixed circuit at export-time values; keep its matching JSON sidecar to recover original source and dependencies. This unitary export does not support observation or training programs.

Sweep parameters together

A paramgroup selects existing parameters for one experiment. In the Playground Sweep tab, choose the group, enter each axis's values and inspect the total point count before running.

main.nm

module grouped_rotations;param theta:Angle=0.3;param phi:Angle=0.4;paramgroup angles=[theta,phi];fn main(){  let q=qreg[1];  Ry(q[0],theta);  Rx(q[0],phi);  let cost=expect Z(q[0]);  return cost;}

  1. Open the example and choose Load source and context if prompted. On mobile, switch to Results.
  2. Enter 0, 0.4 for theta and 0, 0.5 for phi. All combinations creates four points; pairing by position creates two.
  3. Run the group sweep. Each point shows its starting values and cost. This example computes cos(theta) × cos(phi); the last point is approximately 0.808307.
  4. Open a point in the debugger, step through it or continue to the end. Download and reopen its record; the main editor source stays unchanged.

Use a dot for decimals and commas or spaces between values. Zip axes must have equal lengths. At most 256 points are admitted; training and gradients can exhaust the shared work budget earlier. Cancellation never presents unfinished work as success. Debugging inspects the selected initial circuit, not the final trained weights.

Open parameter sweep example

Matrices and shape safety

Transpose, multiply and compute norms of immutable Float and Complex matrices. Each dimension must be between 1 and 16. The matrix constructor requires literal Int dimensions and row-major Float elements. Arrays do not implicitly convert to matrices.

  • complex_matrix(rows: Int, columns: Int, reals: Array<Float, R*C>, imaginaries: Array<Float, R*C>) -> Matrix<Complex, R, C>

    Row-major Complex matrix from two Float buffers; dimensions must be literal Int values from 1 to 16.

  • conjugate_transpose(value: Matrix<Complex, R, C>) -> Matrix<Complex, C, R>

    Swap rows and columns and negate imaginary components.

  • matrix(rows: Int, columns: Int, values: Array<Float, R*C>) -> Matrix<Float, R, C>

    Row-major matrix; dimensions must be literal Int values from 1 to 16.

  • transpose(value: Matrix<T, R, C>) -> Matrix<T, C, R>

    Swap rows and columns without changing elements. T is Float or Complex; operands must share the same element type.

  • matmul(left: Matrix<T, R, K>, right: Matrix<T, K, C>) -> Matrix<T, R, C>

    Matrix product; inner dimensions must match. T is Float or Complex; operands must share the same element type.

  • matvec(matrix: Matrix<Float, R, C>, vector: Array<Float, C>) -> Array<Float, R>

    Matrix-vector product with matching column count and vector length.

  • trace(value: Matrix<T, N, N>) -> T

    Sum of diagonal elements; requires a square matrix. T is Float or Complex; operands must share the same element type.

  • frobenius(value: Matrix<T, R, C>) -> Float

    Stable Frobenius norm; a parameter-dependent zero norm has no derivative. T is Float or Complex; operands must share the same element type.

  • matrix_at(value: Matrix<T, R, C>, row: Int, column: Int) -> T

    Checked zero-based element access; derivative indices must be parameter-independent. T is Float or Complex; operands must share the same element type.

  • complex(re: Float, im: Float) -> Complex

    Construct a Complex value from real and imaginary Float components.

  • real(value: Complex) -> Float

    Read the real component.

  • imag(value: Complex) -> Float

    Read the imaginary component.

  • conj(value: Complex) -> Complex

    Negate the imaginary component.

  • complex_add(left: Complex, right: Complex) -> Complex

    Add two Complex values.

  • complex_sub(left: Complex, right: Complex) -> Complex

    Subtract the right Complex value from the left.

  • complex_mul(left: Complex, right: Complex) -> Complex

    Multiply two Complex values.

  • complex_div(left: Complex, right: Complex) -> Complex

    Divide Complex values; a zero divisor reports an error.

  • complex_abs(value: Complex) -> Float

    Stable magnitude; a parameter-dependent zero has no derivative.

  • complex_abs2(value: Complex) -> Float

    Squared magnitude, differentiable at zero.

Helpers infer up to three const Int dimensions from inputs. Products must agree on their shared K dimension; trace requires a square matrix. Dimensions are type-only; matrix(R,C,...) is invalid. R*C in the catalogue describes the element count, not supported type syntax.

Indices are zero-based and checked in both dimensions. Every intermediate product/sum obeys Float bounds and the shared work budget. A valid 16×16 shape does not guarantee every operation fits the budget. Typed Matrix Result payloads follow the compound Result contract. Quantum-only export of this example is unsupported; compute/apply unitary export has a separate contract.

Matrix operations retain the chain rule in gate angles, adjoint training and custom losses. A parameter-dependent zero Frobenius norm has an undefined derivative and reports an error; a constant zero matrix has zero derivative. Differentiated indices must be parameter-independent.

The example produces gram rows [14, 32] and [32, 77], with answer equal to 77. The Playground link enables typedClassicalValues and classicalFunctions.

Matrix<Float, R, C>

module matrix_example;fn flip<const R:Int,const C:Int>(a:Matrix<Float,R,C>)->Matrix<Float,C,R>{  return transpose(a);}fn product<const R:Int,const K:Int,const C:Int>(a:Matrix<Float,R,K>,b:Matrix<Float,K,C>)->Matrix<Float,R,C>{  return matmul(a,b);}fn main(){  let q=qreg[1];  let a:Matrix<Float,2,3>=matrix(2,3,[1.0,2.0,3.0,4.0,5.0,6.0]);  let gram:Matrix<Float,2,2>=product(a,flip(a));  let answer:Float=matrix_at(gram,1,1);  H(q[0]);}

Open matrix example

The Complex example produces Gram matrix [[6, 3+i], [3-i, 3]] and trace 9+0i. complex_matrix takes two separate Float buffers. T means Float or Complex; mixed matrix products and Complex matvec are unsupported. complex_abs2 is differentiable at zero; parameter-dependent zero complex_abs reports an error.

Matrix<Complex, R, C>

module complex_gram;fn dag<const R:Int,const C:Int>(a:Matrix<Complex,R,C>)->Matrix<Complex,C,R>{  return conjugate_transpose(a);}fn main(){  let q=qreg[1];  let a:Matrix<Complex,2,2>=complex_matrix(2,2,[1.0,2.0,0.0,1.0],[1.0,0.0,1.0,-1.0]);  let gram:Matrix<Complex,2,2>=matmul(a,dag(a));  let total:Complex=trace(gram);  Ry(q[0],real(total)/10.0);}

Open Complex matrix example

Helpers in measured branches

Use typed locals, helper calls and math gate angles in the selected main branch. With classicalFunctions and typedClassicalValues enabled, Bool values and Bool-returning helpers support nested if/else; existing measurement conditions remain available.

The example enables classicalFunctions, typedClassicalValues and functionResults. X makes the measurement 1: the result is angle ≈ 0.479426. Remove X(q[0]) to return ZERO_MEASUREMENT. Change checked_angle(0.5)? to checked_angle(-0.5)? to return NEGATIVE_ANGLE before the Ry gate. Only the selected branch runs; an early Result return stops subsequent operations.

Open measured helper example

measured → checked_angle → Result<Float, Error>

module measured_helpers; fn checked_angle(value: Float) -> Result<Float, Error> {  if (value < 0.0) {    return error("NEGATIVE_ANGLE");  }  return ok(sin(value));} fn main() -> Result<Float, Error> {  let q = qreg[2];  X(q[0]);  let measured: Bit = measure(q[0]);  if (measured == 1) {    let angle: Float = checked_angle(0.5)?;    Ry(q[1], cos(angle));    return ok(angle);  } else {    return error("ZERO_MEASUREMENT");  }}

Block locals stay inside their block. Sibling branches may reuse a name; shadowing an outer binding is rejected. Editor completion, type information, references and rename preserve these boundaries. Rename requires complete, valid source. Int/Float let mut and constant-range for loops share a 4096-step execution budget; unbounded repetition is unsupported.

Statevector supports helpers in observe/test/snapshot blocks. MPS explicitly rejects these report blocks and until; stabilizer falls back to statevector for report blocks. Projecting dynamically controlled helper calls to QASM or framework circuits is rejected with NM-CFN-011; keep the original N/M source.

Main loops, dynamic indices and Results

The example links enable classicalFunctions, typedClassicalValues and functionResults together. The source runs on statevector, MPS and stabilizer while retaining each engine's gate capabilities.

Read an array, update a total, select a qubit

The result is ok(4.5), with quantum state |11>. In this example weights is immutable and total changes. The upper bound of 0..2 is exclusive. weights[i] and q[i] use the current Int index on each iteration. Change the range to 0..3 to trigger an array bounds error on the third iteration; no third X runs. Earlier gates are not rolled back.

Open in Playground

Read an array, update a total, select a qubit

module indexed_accumulation;fn main() -> Result<Float, Error> {  let q = qreg[2];  let weights: Array<Float, 2> = [1.5, 3.0];  let mut total: Float = 0.0;  for i in 0..2 {    total = total + weights[i];    X(q[i]);  }  return ok(total);}

Store a Result in main and handle both outcomes

checked(1.5) returns ok(3) and runs X. checked(-1.5) preserves error("NEGATIVE") and skips X. Both match arms are required; value is visible only in the ok arm. return attempt preserves the original error code. Alternatively, let value: Float = attempt?; inside a Result-returning main stops later operations on error. To recover deliberately, use return ok(0.0); in the error arm.

Open in Playground

Store a Result in main and handle both outcomes

module main_result_recovery;fn checked(value: Float) -> Result<Float, Error> {  if (value < 0.0) { return error("NEGATIVE"); }  return ok(value * 2.0);}fn main() -> Result<Float, Error> {  let q = qreg[1];  let attempt: Result<Float, Error> = checked(1.5);  match (attempt) {    ok(value) => {      X(q[0]);      return ok(value);    }    error => {      return attempt;    }  }}

Differentiate the updated total

total and cursor change each iteration; the array remains immutable. The final angle is 4*theta + theta*theta. At theta=0.3, slope = -sin(1.29)*4.6. Adjoint differentiation preserves assignment order. Indices and conditions must be parameter-independent; a zero partial alone does not establish independence.

Open in Playground

Differentiate the updated total

module mutation_gradient;param theta:Angle=0.3;fn main(){ let q=qreg[1]; let xs:Array<Float,3>=[theta,theta*theta,theta*3.0]; let mut total:Float=0.0; let mut cursor:Int=2; for i in 0..3 {  total=total+xs[cursor];  cursor=cursor-1; } Ry(q[0],total); let cost=expect Z(q[0]); let slope=gradient cost wrt theta; return slope;}

Replace array elements and retain the old copy

Use let mut to replace elements or entire fixed Int/Float arrays. An xs[i] assignment checks its index first; a failure preserves the old value. original retains the earlier copy. The final angle is theta + theta² + theta³; at theta=0.3, slope = -sin(0.417)*1.87. Differentiation replaces only the changed element's derivative.

Open in Playground

Replace array elements and retain the old copy

module array_mutation_gradient;param theta:Angle=0.3;fn main(){ let q=qreg[1]; let mut xs:Array<Float,2>=[theta,theta*theta]; let original:Array<Float,2>=xs; for i in 0..2 {  xs[i]=xs[i]*theta; } xs[0]=xs[0]+original[0]; Ry(q[0],xs[0]+xs[1]); let cost=expect Z(q[0]); let slope=gradient cost wrt theta; return slope;}

Loop bounds are constant integers: 0 ≤ start ≤ end ≤ 256. Nested loops, expressions and helpers share a 4096-step budget. Gate targets must be Int, stay within their register and be distinct for multi-target gates. Dynamic indexing does not extend to measurement/reset targets.

Main Result payloads may be Int, Float, Bool, Bit or fixed Int/Float/Bit arrays; nested Results and Bool arrays are unsupported. Bit is not implicitly converted to Int. Numeric helpers accept Result parameters, but Bit payloads need explicit conversion. A Result error is a successful engine execution; out-of-bounds access or division by zero is a runtime failure.

N/M source and JSON IR preserve these examples. Scalar main assignments, bounded loops and parameter-independent array reads support adjoint training; parameter-independent Int qubit targets also work with bounds checks. QASM/framework export and optimization reject this dynamic flow. The static circuit view shows unresolved targets as q[?] and reports uncertain resource bounds when they cannot be determined reliably.

Store Results and recover inside helpers

Store a Result<T, Error> in a typed local inside a pure helper; handle both ok(value) and error explicitly with match (attempt). Each arm must appear exactly once, in either order. The selector must name a Result local. The ok payload has type T and stays inside its arm; the error arm does not introduce an error variable.

The example enables all three experimental features. recover(-1.0) deliberately converts NEGATIVE_ROOT into Float: 0. recover(4.0) returns Float: 2. Replace the main call with forward(-1.0)? to preserve the original NEGATIVE_ROOT code and return before Ry executes. Errors do not silently become success; the source explicitly chooses a fallback value.

Open Result recovery example

Result → match → fallback / original error

module result_recovery; fn checked_root(value: Float) -> Result<Float, Error> {  if (value < 0.0) {    return error("NEGATIVE_ROOT");  }  return ok(sqrt(value));} fn recover(value: Float) -> Float {  let attempt: Result<Float, Error> = checked_root(value);  match (attempt) {    ok(root) => {      return root;    }    error => {      return 0.0;    }  }} fn forward(value: Float) -> Result<Float, Error> {  let attempt: Result<Float, Error> = checked_root(value);  match (attempt) {    ok(root) => {      return ok(root);    }    error => {      return attempt;    }  }} fn main() -> Result<Float, Error> {  let q = qreg[1];  let value: Float = recover(-1.0);  Ry(q[0], value);  return ok(value);}

Result locals and exhaustive two-arm matches work in helpers and main with all three experimental flags. Helpers also accept Result parameters. Nested Results remain unsupported. T may be Int, Float, Bool or a fixed Int/Float array. Local attempt? unwraps success or forwards the original error in a Result-returning helper or main, for example return ok(attempt?);. The error arm may return attempt; to preserve its original code. The ok binding counts toward the 32-local limit. Analytic gradients of helpers in this native Error Result example are unsupported. Typed-error recovery derivatives have a separate supported contract.

The editor shows Result-local and ok-payload types. Completion, definitions, references and rename preserve arm boundaries and leave another helper's same-named value alone. Renames that shadow an outer binding are rejected.

Carry records and enums in Results

A successful helper can return a record or enum instead of a single number. In Result<Sample,Problem>, Sample is the success type and Problem is the error enum. A match opens the record fields in its success arm or the error variant in its error arm. Module identities remain distinct across file boundaries.

checked(0.5) produces a record with energy 0.25, giving a circuit expectation of cos(0.25). Change the input to -1.0 to run only the error arm with the explicitly chosen angle 0.5. The example enables all three experimental features.

Open record Result example

Result<Sample,Problem>

module aggregate_result;record Sample {energy:Float}enum Problem {Negative} fn checked(value:Float)->Result<Sample,Problem>{ if(value<0.0){return error(Problem.Negative);} return ok(Sample {energy:value*value});} fn main(){ let q=qreg[1]; let attempt:Result<Sample,Problem>=checked(0.5); match(attempt){  ok(sample)=>{Ry(q[0],sample.energy);}  error(reason)=>{Ry(q[0],0.5);} } let cost=expect Z(q[0]); return cost;}

The success record and error enum can also live in different files. The four-file example uses record and enum Results together, producing cos(0.5). Change both 0.5 inputs in the entry file to -1.0 to take the error arms and produce cos(0.75).

main can also return the record as its program result. This example returns energy: 0.25. Change the checked input to -1.0 and ? returns the error; the first X gate remains applied and the second X is skipped. Returning a typed program error is different from a simulator execution failure.

Array, Complex and matrix results

This example returns a two-element Complex matrix: complex(0.25, 0) and complex(0.5, 1). Change the checked input to -1.0 to return Negative; the first X is applied and the second X is skipped. Array length, matrix dimensions and element type remain checked on both success and error paths.

Open matrix Result example

Result<Matrix<Complex,1,2>,Problem>

module compound_result_lesson;enum Problem {Negative}fn checked(x:Float)->Result<Matrix<Complex,1,2>,Problem>{ if(x<0.0){return error(Problem.Negative);} return ok(complex_matrix(1,2,[x,0.5],[0.0,1.0]));}fn main()->Result<Matrix<Complex,1,2>,Problem>{ let q=qreg[1]; X(q[0]); let value:Matrix<Complex,1,2>=checked(0.25)?; X(q[0]); return ok(value);}

Record/enum and numeric array/Complex/matrix success values work in helper returns, Result parameters, main locals and main program results. In Result-returning functions with the same error enum, ? unwraps success or forwards the error. Generic record and enum types are shown in the example below. Nested Results remain unsupported. Numeric derivatives through typed-error recovery are supported; parameter-dependent discrete branch choices are rejected.

Generic records and enums carrying values

Box<T> stores different types in one record shape: Box<Sample> holds a sample and Box<Int> holds an integer. Problem<T>.Bad(T) returns a value with an error. Type arguments can be nested; each field type is checked during compilation.

In Playground, load the source and context, then select Run. attempt(false) returns a record containing energy: 0.25. The two X gates cancel, leaving the final state |0⟩.

Replace attempt(false) with attempt(true) and run again. The error carries 7 inside Box<Int>. The ? operator ends the program before the second X gate, leaving |1⟩. This is a typed program result, distinct from a simulator execution failure.

model.nm

module model;pub record Box<T> { value: T }

values.nm

module values;pub record Sample { energy: Float }

problem.nm

module problem;pub enum Problem<T> { Missing, Bad(T) }

app.nm

module app;use workspace.model as m;use workspace.values as v;use workspace.problem as e; fn attempt(failed: Bool) -> Result<m.Box<v.Sample>, e.Problem<m.Box<Int>>> {  if (failed) {    return error(e.Problem<m.Box<Int>>.Bad(m.Box<Int> { value: 7 }));  }  return ok(m.Box<v.Sample> { value: v.Sample { energy: 0.25 } });} fn main() -> Result<m.Box<v.Sample>, e.Problem<m.Box<Int>>> {  let q = qreg[1];  X(q[0]);  let sample: m.Box<v.Sample> = attempt(false)?;  X(q[0]);  return ok(sample);}

Navigate to uses of Box, rename the record’s value field or inspect the payload type inside a .Bad( call. Enum variables opened by match are visible only in their own arm. Cross-file rename validates the affected code.

m, v and e are file aliases. Aliases preserve type identity; a Sample with identical fields in another module is a different type. Only pub declarations are available to other files.

Experimental. Supports up to 4 type parameters per declaration, 8 payload items per enum variant and 256 concrete nominal types per compilation. Recursive types and nested Results are unsupported. Numeric derivatives through nested records, enum payloads and typed-error recovery are supported; parameter-dependent discrete decisions are rejected. Main scalar/array mutation derivatives follow the main runtime contract on this page.

From nested values to gate angles and derivatives

The angle passes through Box → Choice.Some → Box. Load source and context, then run: slope describes how the Z expectation changes with theta. The expected derivative is −sin(0.29/1.04) × 0.6/1.04. match opens only the selected payload and preserves its numeric derivative.

nested_gradient.nm

module nested_gradient;param theta:Angle=0.3;param phi:Angle=0.2;record Box<T>{value:T}enum Choice<T>{None,Some(T)}fn make(x:Float,y:Float)->Box<Choice<Box<Float>>> {  return Box<Choice<Box<Float>>>{value:Choice<Box<Float>>.Some(Box<Float>{value:(x*x+y)/(y*y+1.0)})};}fn angle(item:Box<Choice<Box<Float>>>)->Float {  return match(item.value){Choice<Box<Float>>.None=>0.0,Choice<Box<Float>>.Some(sample)=>sample.value};}fn main(){  let q=qreg[1];  let item:Box<Choice<Box<Float>>>=make(theta,phi);  Ry(q[0],angle(item));  let cost=expect Z(q[0]);  let slope=gradient cost wrt theta;  return slope;}

Recovery and derivatives from error payloads

checked carries theta² inside an error. Propagation skips the remaining calculation; recover doubles the payload. The gate angle is 2 × theta². Run the example to obtain slope ≈ −0.214835488111. Error-branch selection is independent of the parameter; the number inside the error retains its derivative.

recovery_gradient.nm

module recovery_gradient;param theta:Angle=0.3;record Box<T>{value:T}enum Problem<T>{Bad(T),Empty}fn checked(x:Float)->Result<Box<Float>,Problem<Box<Float>>>{ return error(Problem<Box<Float>>.Bad(Box<Float>{value:x*x}));}fn forwarded(x:Float)->Result<Float,Problem<Box<Float>>>{ let item:Box<Float>=checked(x)?; return ok(item.value*3.0);}fn recover(result:Result<Float,Problem<Box<Float>>>)->Float{ match(result){  ok(value)=>{return value;}  error(reason)=>{return match(reason){Problem<Box<Float>>.Bad(item)=>item.value*2.0,Problem<Box<Float>>.Empty=>0.0};} }}fn main(){ let q=qreg[1]; let result:Result<Float,Problem<Box<Float>>>=forwarded(theta); Ry(q[0],recover(result)); let cost=expect Z(q[0]); let slope=gradient cost wrt theta; return slope;}

Bounded helper loops and mutable numeric locals

Inside a pure helper, explicitly declare let mut Int or Float locals and assign values of the same type. for i in 0..4 runs with i = 0, 1, 2, 3; the end is exclusive. Bounds must be literal integers from 0 through 256. Equal bounds mean zero iterations; reversed ranges are rejected.

The example sums four Floats: [0.1, 0.2, 0.3, 0.4] → Float: 1. Change 0..4 to 0..0 for a zero total. Change it to 0..5 and values[4] raises a bounds error rather than reading a silent value. The example enables classicalFunctions and typedClassicalValues, plus functionResults for main's Result return.

Open bounded sum example

sum_values(Array<Float, 4>) → Float: 1

module bounded_sum; fn sum_values(values: Array<Float, 4>) -> Float {  let mut sum: Float = 0.0;  for i in 0..4 {    sum = sum + values[i];  }  return sum;} fn main() -> Result<Float, Error> {  let q = qreg[1];  let values: Array<Float, 4> = [0.1, 0.2, 0.3, 0.4];  let total: Float = sum_values(values);  Ry(q[0], total);  return ok(total);}

The inferred Int loop index is immutable and visible only in its body. Parameters, ordinary let locals, ok payloads and arrays remain immutable. Only let mut Int/Float locals may be reassigned; there is no implicit Int/Float coercion. Loop locals are recreated per iteration, cannot escape their block or shadow outer names. A fallback return is required after the loop; a return only inside the loop is insufficient. Nested loops and helper calls share the 4096 evaluation-step budget; no while syntax is added.

Helper array indices may be any Int expression: a parameter, local, loop index or calculation. Negative or out-of-range indices fail at runtime. Main also supports runtime Int indices with classicalFunctions and typedClassicalValues; see the main runtime examples on this page. Analytic training supports literal-bound helper loops in 0–256, Int/Float accumulation and bounds-checked Int indices independent of the differentiated input. Nested calls and loops share the 4096-step budget. Parameter-dependent discrete decisions/indices and runtime-dependent loop bounds are rejected. Typed Result recovery and bounded main-loop derivatives follow their separate contracts.

The editor marks mut locals as mutable and loop indices as readonly. References and rename follow both reads and assignment targets while leaving a sibling loop's same-named index unchanged.

Separate same-named helpers with module aliases

use workspace.scale as linear; exposes helpers only through linear.adjust(...). Two modules may export the same adjust name; an aliased import does not introduce a flat adjust(...) name. Here linear.adjust(0.25) and shifted.adjust(0.25) each return 0.5 from their own module, producing Float: 1.

The transfer carries the entry source and both scale and offset modules. Explicitly load source and context in Playground; the classicalFunctions, typedClassicalValues, functionResults and moduleVisibility features are shown before loading. Run it yourself afterward. Transfer never starts execution automatically.

alias_demo.nm → Float: 1

module alias_demo;use workspace.scale as linear;use workspace.offset as shifted; fn main() -> Result<Float, Error> {  let q = qreg[1];  let first: Float = linear.adjust(0.25);  let second: Float = shifted.adjust(0.25);  let angle: Float = first + second;  Ry(q[0], angle);  return ok(angle);}

scale.nm

module scale;pub fn adjust(value: Float) -> Float {  return value * 2.0;}

offset.nm

module offset;private fn offset_value(value: Float) -> Float {  return value + 0.25;}pub fn adjust(value: Float) -> Float {  return offset_value(value);}

An alias belongs only to its importing module. Private helpers and transitive dependencies remain hidden; shifted.offset_value(...) is rejected. Aliases cannot shadow another helper, parameter or local binding. This syntax imports pure helpers; it does not add general module objects or re-exports.

Completion after linear. lists only accessible members. F12 on a member opens its original helper declaration; F12 on the alias opens the import declaration. Renaming a member updates the original helper and its consumers; renaming an alias updates only that module's import and qualifiers. Source-mapped debugging preserves original module lines.

Use records and typed errors across files

One file defines the Sample record and Problem error enum; the entry program uses them through alias a. Aliases preserve type identity. The first X runs, then the negative input makes ? return an error from main before the second X.

main.nm

module workspace_import_example;use workspace.imported_types as a;@target("browser-statevector");fn main()->Result<Float,a.Problem>{ let q=qreg[1]; let sample:a.Sample=a.make(0.5); let angle:Float=a.read(sample); if(angle>0.0){X(q[0]);} let input:Result<Float,a.Problem>=a.checked(-1.0); let value:Float=input?; X(q[0]); return ok(value);}

imported_types.nm

module imported_types;pub record Sample {energy:Float}pub enum Problem {Negative}private fn square(x:Float)->Float{return x*x;}pub fn make(x:Float)->Sample{return Sample{energy:square(x)};}pub fn read(sample:Sample)->Float{return sample.energy;}pub fn checked(value:Float)->Result<Float,Problem>{ if(value<0.0){return error(Problem.Negative);} return ok(value);}

After running, open Workspace files and replace the library's return error(Problem.Negative); with return ok(0.4);, then run again. The second X now runs too and the program returns Float: 0.4.

Experimental. Load source and context before running. Private members remain hidden. Result helper returns, main locals and main program results accept scalar, record, enum, fixed-size numeric array, Complex or matrix successes. Generic records/enums also work across files. Cross-file completion, definitions and validated rename are supported.

Develop the loss function in a separate file

losses.nm stores the prediction–label residual in a record and squares it. The entry program trains with a.loss for two updates. Results match builtin MSE on the same circuit; this is a simulator exercise.

Load source and context, then choose Run. Inspect a.loss, training history and validation/test losses. Edit losses.nm under Workspace files and run again to use the changed library.

main.nm

module learner;use workspace.losses as a;@dataset("moons_2d",split:"train");param theta:Angle=0.37;fn main(){ let q=qreg[2]; encode(sample_row(0),q,method:"angle"); Ry(q[0],theta); let cost=expect Z(q[0]); train { objective:minimize cost; optimizer:gd(lr=0.1); steps:2; dataset:moons_2d; loss:custom(a.loss); validation: split = validation, every = 1; checkpoint: best(metric = validation_loss); } return cost;}

losses.nm

module losses;pub record Residual {delta:Float}enum State {Ready,Failed}private fn squared(item:Residual)->Float{return item.delta*item.delta;}pub fn loss(p:Float,y:Float)->Float { let item:Residual=Residual {delta:p-y}; return match(State.Ready){State.Ready=>squared(item),State.Failed=>1.0/0.0};}

To inspect an error location, replace State.Ready=>squared(item) with State.Ready=>1.0/0.0 in the library. Training stops and the error card identifies the function entry in module losses. Restore the expression and run again.

Experimental, noiseless statevector training. The loss takes two Floats and returns Float. Typed Result recovery derivatives are supported; prediction-dependent discrete decisions are rejected. Run again after editing a library; previous results do not describe the new source.

This is an experimental simulator feature. It does not imply quantum hardware execution or a published package release.