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-experimentalOne 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: errorDeterministic 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-returnTyped measurement result
1module typed_result;2 3fn main() -> Result<Int, Error> {4 let q: QReg<1> = qreg[1];5 H(q[0]);6 let measured: Bit = measure(q[0]);7 let encoded: Int = bit_to_int(measured);8 return ok(encoded);9}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 --jsoninitializationOptions.nm.experimental.functionResults = truenm.experimental.functionResults = truePlayground: on in new sessions; saved settings are preservedStable fail-closed diagnostics
NM-PARSE-066NM-PARSE-067NM-TYPE-025NM-TYPE-026NM-RUNTIME-033Named 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 functionsaverage(samples) → Float: 1
1module classical_runtime;2 3fn double(x: Int) -> Int {4 return x * 2;5}6 7fn square(x: Float) -> Float {8 return x * x;9}10 11fn energy(theta: Float, weight: Float) -> Float {12 let scaled: Float = weight * square(theta);13 return scaled + 0.5;14}15 16fn observations(theta: Float, enabled: Bool) -> Array<Float, 2> {17 if (!enabled) {18 return [0.0, 0.0];19 }20 return [energy(theta, int_to_float(double(1))), square(1.0)];21}22 23fn average(values: Array<Float, 2>) -> Float {24 return mean(values);25}26 27fn checked_observations(theta: Float, enabled: Bool) -> Result<Array<Float, 2>, Error> {28 if (theta < 0.0) {29 return error("NEGATIVE_THETA");30 }31 return ok(observations(theta, enabled));32}33 34fn main() -> Result<Float, Error> {35 let q = qreg[1];36 let enabled: Bool = true;37 let samples: Array<Float, 2> = checked_observations(0.5, enabled)?;38 let result: Float = average(samples);39 Ry(q[0], average(samples));40 return ok(result);41}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
1module helper_training;2@optimize(method: "adam", steps: 16, learningRate: 0.1, objective: "minimize");3param theta: Angle = 0.8;4 5fn square(x: Float) -> Float {6 return x * x;7}8 9fn main() {10 let q = qreg[1];11 Ry(q[0], square(theta));12 let cost = expect Z(q[0]);13 return cost;14}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
1module helper_training;2@optimize(method: "adam", steps: 16, learningRate: 0.1, objective: "minimize");3use workspace.helpers;4param theta: Angle = 0.8;5 6fn main() {7 let q = qreg[1];8 Ry(q[0], square(theta));9 let cost = expect Z(q[0]);10 return cost;11}Dependency: helpers.nm
1module helpers;2fn square(x: Float) -> Float {3 return x * x;4}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
1module custom_training;2@dataset("moons_2d", split: "train");3param theta: Angle = 0.37;4fn residual(prediction: Float, label: Float) -> Float {5 let mut total: Float = 0.0;6 for i in 0..2 {7 total = total + (prediction - label) * (prediction - label);8 }9 return total;10}11fn main() {12 let q = qreg[2];13 encode(sample_row(0), q, method: "reupload");14 Ry(q[0], theta);15 let cost = expect Z(q[0]);16 train {17 objective: minimize cost;18 optimizer: gd(lr = 0.1);19 steps: 2;20 dataset: moons_2d;21 loss: custom(residual);22 }23 return cost;24}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— Minimummax(Float, Float) -> Float— Maximumclamp(Float, Float, Float) -> Float— Clamp to inclusive boundssum(Array<T, N>) -> T— Checked sumdot(Array<T, N>, Array<T, N>) -> T— Inner productnorm(Array<Float, N>) -> Float— Euclidean normadd(Array<T, N>, Array<T, N>) -> Array<T, N>— Elementwise additionsub(Array<T, N>, Array<T, N>) -> Array<T, N>— Elementwise subtractionscale(Array<T, N>, T) -> Array<T, N>— Scalar multiplicationvariance(Array<Float, N>) -> Float— Population variancestddev(Array<Float, N>) -> Float— Population standard deviationargmin(Array<T, N>) -> Int— First minimum indexargmax(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 exampleFloat / Array → Float
1module float_math;2param theta: Angle = 0.4;3 4fn bend(x: Float) -> Float {5 return dot([sin(x), cos(x)], [1.0, 0.25]);6}7 8fn main() {9 let q = qreg[1];10 let scale: Float = sqrt(int_to_float(4));11 let wave: Float = sin(theta);12 let probability: Float = sigmoid(theta);13 let activation: Float = tanh(theta);14 let features: Array<Float, 2> = [wave, activation];15 let magnitude: Float = norm(features);16 let spread: Float = variance(features);17 let bounded: Float = clamp(probability, 0.0, 1.0);18 Ry(q[0], bend(theta) / scale);19 Rz(q[0], cos(theta));20 let cost = expect Z(q[0]);21 return cost;22}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 PlaygroundDegrees, radians and derivatives
1module angle_units;2param theta:Angle=30.0;3fn heading(value:Float)->Angle{return degrees(value);}4fn main(){5 let q=qreg[1];6 let angle:Angle=heading(theta);7 let radians_value:Float=to_radians(angle);8 let degrees_value:Float=to_degrees(angle);9 Ry(q[0],radians(radians_value));10 let cost=expect Z(q[0]);11 let slope=gradient cost wrt theta;12 return cost;13}Carry Angle through a record and Result
Open in PlaygroundCarry Angle through a record and Result
1module angle_result;2param theta:Angle=30.0;3record Heading{angle:Angle}4enum Problem{Unavailable}5fn checked(value:Float)->Result<Heading,Problem>{6 return ok(Heading{angle:degrees(value)});7}8fn main()->Result<Angle,Problem>{9 let q=qreg[1];10 let item:Heading=checked(theta)?;11 Ry(q[0],item.angle);12 let cost=expect Z(q[0]);13 let slope=gradient cost wrt theta;14 return ok(item.angle);15}Return an Angle with a builtin error code
Open in PlaygroundReturn an Angle with a builtin error code
1module angle_builtin_result;2fn checked(value:Float,available:Bool)->Result<Angle,Error>{3 if(!available){return error("UNAVAILABLE");}4 return ok(degrees(value));5}6fn main()->Result<Angle,Error>{7 let q=qreg[1];8 let attempt:Result<Angle,Error>=checked(30.0,true);9 match(attempt){10 ok(angle)=>{Ry(q[0],angle);}11 error=>{X(q[0]);}12 }13 let cost=expect Z(q[0]);14 return attempt;15}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 PlaygroundImplement X with HZH
1module compute_demo;2fn main(){3 let q=qreg[1];4 compute {H(q[0]);}5 apply {Z(q[0]);}6}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 PlaygroundDifferentiate the preparation angle
1module compute_gradient;2param theta:Angle=0.3;3fn main(){4 let q=qreg[1];5 compute {Ry(q[0],theta*theta);}6 apply {Z(q[0]);}7 let cost=expect Z(q[0]);8 let slope=gradient cost wrt theta;9 return slope;10}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 PlaygroundReversal does not guarantee zero ancillas
1module ancilla_example;2fn main(){3 let q=qreg[2];4 compute {CNOT(q[0],q[1]);}5 apply {X(q[0]);}6}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
1module grouped_rotations;2param theta:Angle=0.3;3param phi:Angle=0.4;4paramgroup angles=[theta,phi];5fn main(){6 let q=qreg[1];7 Ry(q[0],theta);8 Rx(q[0],phi);9 let cost=expect Z(q[0]);10 return cost;11}- Open the example and choose Load source and context if prompted. On mobile, switch to Results.
- Enter 0, 0.4 for theta and 0, 0.5 for phi. All combinations creates four points; pairing by position creates two.
- 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.
- 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 exampleMatrices 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>) -> TSum 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>) -> FloatStable 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) -> TChecked 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) -> ComplexConstruct a Complex value from real and imaginary Float components.
real(value: Complex) -> FloatRead the real component.
imag(value: Complex) -> FloatRead the imaginary component.
conj(value: Complex) -> ComplexNegate the imaginary component.
complex_add(left: Complex, right: Complex) -> ComplexAdd two Complex values.
complex_sub(left: Complex, right: Complex) -> ComplexSubtract the right Complex value from the left.
complex_mul(left: Complex, right: Complex) -> ComplexMultiply two Complex values.
complex_div(left: Complex, right: Complex) -> ComplexDivide Complex values; a zero divisor reports an error.
complex_abs(value: Complex) -> FloatStable magnitude; a parameter-dependent zero has no derivative.
complex_abs2(value: Complex) -> FloatSquared 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>
1module matrix_example;2fn flip<const R:Int,const C:Int>(a:Matrix<Float,R,C>)->Matrix<Float,C,R>{3 return transpose(a);4}5fn product<const R:Int,const K:Int,const C:Int>(a:Matrix<Float,R,K>,b:Matrix<Float,K,C>)->Matrix<Float,R,C>{6 return matmul(a,b);7}8fn main(){9 let q=qreg[1];10 let a:Matrix<Float,2,3>=matrix(2,3,[1.0,2.0,3.0,4.0,5.0,6.0]);11 let gram:Matrix<Float,2,2>=product(a,flip(a));12 let answer:Float=matrix_at(gram,1,1);13 H(q[0]);14}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>
1module complex_gram;2fn dag<const R:Int,const C:Int>(a:Matrix<Complex,R,C>)->Matrix<Complex,C,R>{3 return conjugate_transpose(a);4}5fn main(){6 let q=qreg[1];7 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]);8 let gram:Matrix<Complex,2,2>=matmul(a,dag(a));9 let total:Complex=trace(gram);10 Ry(q[0],real(total)/10.0);11}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 examplemeasured → checked_angle → Result<Float, Error>
1module measured_helpers;2 3fn checked_angle(value: Float) -> Result<Float, Error> {4 if (value < 0.0) {5 return error("NEGATIVE_ANGLE");6 }7 return ok(sin(value));8}9 10fn main() -> Result<Float, Error> {11 let q = qreg[2];12 X(q[0]);13 let measured: Bit = measure(q[0]);14 if (measured == 1) {15 let angle: Float = checked_angle(0.5)?;16 Ry(q[1], cos(angle));17 return ok(angle);18 } else {19 return error("ZERO_MEASUREMENT");20 }21}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 PlaygroundRead an array, update a total, select a qubit
1module indexed_accumulation;2fn main() -> Result<Float, Error> {3 let q = qreg[2];4 let weights: Array<Float, 2> = [1.5, 3.0];5 let mut total: Float = 0.0;6 for i in 0..2 {7 total = total + weights[i];8 X(q[i]);9 }10 return ok(total);11}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 PlaygroundStore a Result in main and handle both outcomes
1module main_result_recovery;2fn checked(value: Float) -> Result<Float, Error> {3 if (value < 0.0) { return error("NEGATIVE"); }4 return ok(value * 2.0);5}6fn main() -> Result<Float, Error> {7 let q = qreg[1];8 let attempt: Result<Float, Error> = checked(1.5);9 match (attempt) {10 ok(value) => {11 X(q[0]);12 return ok(value);13 }14 error => {15 return attempt;16 }17 }18}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 PlaygroundDifferentiate the updated total
1module mutation_gradient;2param theta:Angle=0.3;3fn main(){4 let q=qreg[1];5 let xs:Array<Float,3>=[theta,theta*theta,theta*3.0];6 let mut total:Float=0.0;7 let mut cursor:Int=2;8 for i in 0..3 {9 total=total+xs[cursor];10 cursor=cursor-1;11 }12 Ry(q[0],total);13 let cost=expect Z(q[0]);14 let slope=gradient cost wrt theta;15 return slope;16}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 PlaygroundReplace array elements and retain the old copy
1module array_mutation_gradient;2param theta:Angle=0.3;3fn main(){4 let q=qreg[1];5 let mut xs:Array<Float,2>=[theta,theta*theta];6 let original:Array<Float,2>=xs;7 for i in 0..2 {8 xs[i]=xs[i]*theta;9 }10 xs[0]=xs[0]+original[0];11 Ry(q[0],xs[0]+xs[1]);12 let cost=expect Z(q[0]);13 let slope=gradient cost wrt theta;14 return slope;15}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 exampleResult → match → fallback / original error
1module result_recovery;2 3fn checked_root(value: Float) -> Result<Float, Error> {4 if (value < 0.0) {5 return error("NEGATIVE_ROOT");6 }7 return ok(sqrt(value));8}9 10fn recover(value: Float) -> Float {11 let attempt: Result<Float, Error> = checked_root(value);12 match (attempt) {13 ok(root) => {14 return root;15 }16 error => {17 return 0.0;18 }19 }20}21 22fn forward(value: Float) -> Result<Float, Error> {23 let attempt: Result<Float, Error> = checked_root(value);24 match (attempt) {25 ok(root) => {26 return ok(root);27 }28 error => {29 return attempt;30 }31 }32}33 34fn main() -> Result<Float, Error> {35 let q = qreg[1];36 let value: Float = recover(-1.0);37 Ry(q[0], value);38 return ok(value);39}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 exampleResult<Sample,Problem>
1module aggregate_result;2record Sample {energy:Float}3enum Problem {Negative}4 5fn checked(value:Float)->Result<Sample,Problem>{6 if(value<0.0){return error(Problem.Negative);}7 return ok(Sample {energy:value*value});8}9 10fn main(){11 let q=qreg[1];12 let attempt:Result<Sample,Problem>=checked(0.5);13 match(attempt){14 ok(sample)=>{Ry(q[0],sample.energy);}15 error(reason)=>{Ry(q[0],0.5);}16 }17 let cost=expect Z(q[0]);18 return cost;19}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 exampleResult<Matrix<Complex,1,2>,Problem>
1module compound_result_lesson;2enum Problem {Negative}3fn checked(x:Float)->Result<Matrix<Complex,1,2>,Problem>{4 if(x<0.0){return error(Problem.Negative);}5 return ok(complex_matrix(1,2,[x,0.5],[0.0,1.0]));6}7fn main()->Result<Matrix<Complex,1,2>,Problem>{8 let q=qreg[1];9 X(q[0]);10 let value:Matrix<Complex,1,2>=checked(0.25)?;11 X(q[0]);12 return ok(value);13}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
1module model;2pub record Box<T> { value: T }values.nm
1module values;2pub record Sample { energy: Float }problem.nm
1module problem;2pub enum Problem<T> { Missing, Bad(T) }app.nm
1module app;2use workspace.model as m;3use workspace.values as v;4use workspace.problem as e;5 6fn attempt(failed: Bool) -> Result<m.Box<v.Sample>, e.Problem<m.Box<Int>>> {7 if (failed) {8 return error(e.Problem<m.Box<Int>>.Bad(m.Box<Int> { value: 7 }));9 }10 return ok(m.Box<v.Sample> { value: v.Sample { energy: 0.25 } });11}12 13fn main() -> Result<m.Box<v.Sample>, e.Problem<m.Box<Int>>> {14 let q = qreg[1];15 X(q[0]);16 let sample: m.Box<v.Sample> = attempt(false)?;17 X(q[0]);18 return ok(sample);19}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
1module nested_gradient;2param theta:Angle=0.3;3param phi:Angle=0.2;4record Box<T>{value:T}5enum Choice<T>{None,Some(T)}6fn make(x:Float,y:Float)->Box<Choice<Box<Float>>> {7 return Box<Choice<Box<Float>>>{value:Choice<Box<Float>>.Some(Box<Float>{value:(x*x+y)/(y*y+1.0)})};8}9fn angle(item:Box<Choice<Box<Float>>>)->Float {10 return match(item.value){Choice<Box<Float>>.None=>0.0,Choice<Box<Float>>.Some(sample)=>sample.value};11}12fn main(){13 let q=qreg[1];14 let item:Box<Choice<Box<Float>>>=make(theta,phi);15 Ry(q[0],angle(item));16 let cost=expect Z(q[0]);17 let slope=gradient cost wrt theta;18 return slope;19}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
1module recovery_gradient;2param theta:Angle=0.3;3record Box<T>{value:T}4enum Problem<T>{Bad(T),Empty}5fn checked(x:Float)->Result<Box<Float>,Problem<Box<Float>>>{6 return error(Problem<Box<Float>>.Bad(Box<Float>{value:x*x}));7}8fn forwarded(x:Float)->Result<Float,Problem<Box<Float>>>{9 let item:Box<Float>=checked(x)?;10 return ok(item.value*3.0);11}12fn recover(result:Result<Float,Problem<Box<Float>>>)->Float{13 match(result){14 ok(value)=>{return value;}15 error(reason)=>{return match(reason){Problem<Box<Float>>.Bad(item)=>item.value*2.0,Problem<Box<Float>>.Empty=>0.0};}16 }17}18fn main(){19 let q=qreg[1];20 let result:Result<Float,Problem<Box<Float>>>=forwarded(theta);21 Ry(q[0],recover(result));22 let cost=expect Z(q[0]);23 let slope=gradient cost wrt theta;24 return slope;25}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 examplesum_values(Array<Float, 4>) → Float: 1
1module bounded_sum;2 3fn sum_values(values: Array<Float, 4>) -> Float {4 let mut sum: Float = 0.0;5 for i in 0..4 {6 sum = sum + values[i];7 }8 return sum;9}10 11fn main() -> Result<Float, Error> {12 let q = qreg[1];13 let values: Array<Float, 4> = [0.1, 0.2, 0.3, 0.4];14 let total: Float = sum_values(values);15 Ry(q[0], total);16 return ok(total);17}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
1module alias_demo;2use workspace.scale as linear;3use workspace.offset as shifted;4 5fn main() -> Result<Float, Error> {6 let q = qreg[1];7 let first: Float = linear.adjust(0.25);8 let second: Float = shifted.adjust(0.25);9 let angle: Float = first + second;10 Ry(q[0], angle);11 return ok(angle);12}scale.nm
1module scale;2pub fn adjust(value: Float) -> Float {3 return value * 2.0;4}offset.nm
1module offset;2private fn offset_value(value: Float) -> Float {3 return value + 0.25;4}5pub fn adjust(value: Float) -> Float {6 return offset_value(value);7}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
1module workspace_import_example;2use workspace.imported_types as a;3@target("browser-statevector");4fn main()->Result<Float,a.Problem>{5 let q=qreg[1];6 let sample:a.Sample=a.make(0.5);7 let angle:Float=a.read(sample);8 if(angle>0.0){X(q[0]);}9 let input:Result<Float,a.Problem>=a.checked(-1.0);10 let value:Float=input?;11 X(q[0]);12 return ok(value);13}imported_types.nm
1module imported_types;2pub record Sample {energy:Float}3pub enum Problem {Negative}4private fn square(x:Float)->Float{return x*x;}5pub fn make(x:Float)->Sample{return Sample{energy:square(x)};}6pub fn read(sample:Sample)->Float{return sample.energy;}7pub fn checked(value:Float)->Result<Float,Problem>{8 if(value<0.0){return error(Problem.Negative);}9 return ok(value);10}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
1module learner;2use workspace.losses as a;3@dataset("moons_2d",split:"train");4param theta:Angle=0.37;5fn main(){6 let q=qreg[2];7 encode(sample_row(0),q,method:"angle");8 Ry(q[0],theta);9 let cost=expect Z(q[0]);10 train {11 objective:minimize cost;12 optimizer:gd(lr=0.1);13 steps:2;14 dataset:moons_2d;15 loss:custom(a.loss);16 validation: split = validation, every = 1;17 checkpoint: best(metric = validation_loss);18 }19 return cost;20}losses.nm
1module losses;2pub record Residual {delta:Float}3enum State {Ready,Failed}4private fn squared(item:Residual)->Float{return item.delta*item.delta;}5pub fn loss(p:Float,y:Float)->Float {6 let item:Residual=Residual {delta:p-y};7 return match(State.Ready){State.Ready=>squared(item),State.Failed=>1.0/0.0};8}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.