How the Nelson Type Overload System Implements Custom Behaviors: A Complete Guide
The Nelson type overload system maps function calls to specialized M-files using a runtime lookup that converts expressions like a + b into @ClassName/plus.m based on operand types.
The nelson-lang/nelson interpreter provides a sophisticated type overload system that allows developers to define custom behaviors for operators and functions on user-defined classes. This mechanism relies on a deterministic naming convention, a runtime dispatcher, and configurable compatibility levels to resolve which implementation executes for any given call.
Core Architecture of the Nelson Type Overload System
The implementation centers on three interconnected components that transform a generic function call into a specific method invocation.
Naming Convention and Symbol Generation
The system generates overload symbols using the pattern @ClassName/FunctionName. This deterministic path construction allows the interpreter to locate the correct M-file without ambiguity.
In modules/overload/src/include/OverloadName.hpp, the getOverloadFunctionName function constructs these symbols:
std::string overloadName = "@" + typeName + "/" + functionName; // e.g., "@complexObj/plus"
This naming convention applies to both named functions and operators. When you write a + b, the parser maps the + operator to the function name plus, then searches for @ClassName/plus.m.
Runtime Dispatch Mechanism
The callOverloadedFunction routine in modules/overload/src/include/OverloadHelpers.hpp serves as the central dispatcher. This function executes several critical steps:
- Checks the
Evaluator::withOverloadflag (defaulttrue) and the currentOverloadLevelCompatibilitysetting - Computes the common type of all arguments to determine which class directory to search
- Builds the overload symbol using
getOverloadFunctionName - Queries
FunctionsInMemoryto verify the M-file exists - Invokes the user-defined function or, for handle objects, calls
HandleGenericObject::invokeMethod
The dispatcher also supports handle objects through a secondary path that invokes C++ methods directly when the overload symbol matches a handle class method.
Module Registration
The overload subsystem registers itself through modules/overload/loader.m, which executes during interpreter startup. This registration ensures the evaluator recognizes the overload subsystem and initializes the necessary function tables.
How Overload Resolution Works at Runtime
When the interpreter encounters a function call with custom types, it follows a deterministic resolution path controlled by compatibility settings.
Compatibility Levels
Nelson provides three overload resolution modes defined in the configuration system (modules/nelson_manager/src/include/NelsonConfiguration.hpp):
NLS_OVERLOAD_NONE(0): Disables all overload resolution; only built-in functions executeNLS_OVERLOAD_OBJECT_TYPES_ONLY(1): Resolves overloads only for user-defined class typesNLS_OVERLOAD_ALL_TYPES(2): Enables overloads for any type, including built-in primitives
The setOverloadLevelCompatibility method stores the selected mode, which callOverloadedFunction checks via a switch statement (lines 73-94 in OverloadHelpers.hpp) before attempting resolution.
Resolution Sequence
For a call like plus(a, b) where a and b are custom objects:
- The parser identifies the function name
plusand argumentsa,b - The evaluator checks if
withOverloadis enabled and verifies the compatibility level permits resolution for these types - The system determines the common type (e.g.,
complexObj) getOverloadFunctionNameconstructs@complexObj/plus- The function table searches for
modules/overload/examples/complex/@complexObj/plus.m - Upon finding the file, the interpreter executes the M-file with
aandbas arguments
Implementing Custom Type Overloads in Nelson
Creating custom behaviors requires organizing M-files into class directories following the @ClassName convention.
Creating a Class with Operator Overloading
To implement a custom complex number class that overloads the + operator:
- Create the class directory structure:
% Save as: modules/overload/examples/complex/@complexObj/complexObj.m
function obj = complexObj(r, i)
obj.r = r; % real part
obj.i = i; % imaginary part
obj = class(obj, 'complexObj');
end
- Implement the addition operator:
% Save as: modules/overload/examples/complex/@complexObj/plus.m
function r = plus(a, b)
% Custom addition for complexObj instances
R1 = a.r + b.r;
R2 = a.i + b.i;
r = complexObj(R1, R2);
end
- Add the class to the path and test:
addpath([nelsonroot(), '/modules/overload/examples/complex']);
a = complexObj(1, 2);
b = complexObj(3, 4);
c = a + b; % Dispatches to @complexObj/plus.m
disp(c);
Overloading Arithmetic Operators
For a custom numeric wrapper that overloads subtraction:
% @myNumber/myNumber.m – constructor
function obj = myNumber(v)
obj.value = v;
obj = class(obj, 'myNumber');
end
% @myNumber/minus.m – binary subtraction overload
function r = minus(a, b)
r = myNumber(a.value - b.value);
end
Usage:
addpath('path/to/@myNumber');
x = myNumber(10);
y = myNumber(3);
z = x - y; % Returns myNumber(7)
disp(z.value); % Output: 7
Configuring Overload Behavior
Control the Nelson type overload system through the configuration API to optimize performance or ensure compatibility.
Runtime Configuration
Adjust overload resolution without restarting the interpreter:
% Disable overloads completely for maximum performance
NelsonConfiguration().setOverloadLevelCompatibility(NLS_OVERLOAD_NONE);
% Enable overloads only for user-defined classes (safer)
NelsonConfiguration().setOverloadLevelCompatibility(NLS_OVERLOAD_OBJECT_TYPES_ONLY);
% Enable full overload support (default)
NelsonConfiguration().setOverloadLevelCompatibility(NLS_OVERLOAD_ALL_TYPES);
Temporary Disabling Pattern
For performance-critical sections where built-in behavior is required:
% Save current setting
prevSetting = NelsonConfiguration().getOverloadLevelCompatibility();
% Turn overloads off
NelsonConfiguration().setOverloadLevelCompatibility(NLS_OVERLOAD_NONE);
% Execute performance-critical code with built-in functions only
result = computeIntensiveOperation(data);
% Restore previous setting
NelsonConfiguration().setOverloadLevelCompatibility(prevSetting);
The Evaluator::withOverload flag (stored in modules/interpreter/src/include/Evaluator.hpp) provides an additional internal check that can disable the entire subsystem when set to false.
Summary
The Nelson type overload system enables custom behaviors through a deterministic runtime lookup mechanism:
- Naming Convention: Overload functions reside in
@ClassName/FunctionName.mfiles, constructed dynamically bygetOverloadFunctionNameinOverloadName.hpp - Runtime Dispatch: The
callOverloadedFunctionroutine inOverloadHelpers.hppresolves calls by computing common argument types and querying the function table - Configuration Control: Three compatibility levels (
NLS_OVERLOAD_NONE,NLS_OVERLOAD_OBJECT_TYPES_ONLY,NLS_OVERLOAD_ALL_TYPES) allow fine-grained control viaNelsonConfiguration - Implementation Pattern: Create class directories, implement methods following the
@ClassName/functionpattern, and add the directory to the Nelson path
Frequently Asked Questions
How does Nelson resolve which overloaded function to call when arguments have different types?
Nelson computes the common type of all arguments to determine the overload resolution path. The callOverloadedFunction dispatcher in OverloadHelpers.hpp examines the types of all operands, identifies the dominant class, and constructs the overload symbol @ClassName/FunctionName based on that common type. If the arguments belong to different user-defined classes, the left-most operand's type typically determines the dispatch according to the interpreter's type precedence rules.
Can I overload built-in operators like + or - for existing primitive types?
Yes, but only when the NLS_OVERLOAD_ALL_TYPES compatibility level is enabled. By default, Nelson uses NLS_OVERLOAD_OBJECT_TYPES_ONLY, which restricts overloading to user-defined classes. To enable primitive type overloading, call NelsonConfiguration().setOverloadLevelCompatibility(NLS_OVERLOAD_ALL_TYPES). However, overloading built-in primitives can impact performance and may cause unexpected behavior in library code, so use this capability cautiously.
What is the performance impact of using type overloads in Nelson?
The Nelson type overload system introduces a small runtime overhead because every function call must check Evaluator::withOverload, compute the common argument type, construct the overload symbol via getOverloadFunctionName, and query FunctionsInMemory for the M-file existence. For performance-critical sections, you can eliminate this overhead by temporarily setting the compatibility level to NLS_OVERLOAD_NONE using NelsonConfiguration().setOverloadLevelCompatibility(), which bypasses all overload resolution logic entirely.
How do I disable overload resolution for debugging purposes?
You can disable the Nelson type overload system globally by setting the compatibility level to NLS_OVERLOAD_NONE through the configuration API. Execute NelsonConfiguration().setOverloadLevelCompatibility(NLS_OVERLOAD_NONE) to prevent the interpreter from searching for @ClassName/FunctionName M-files and force all calls to use built-in implementations. To re-enable overloads later, restore the previous setting or set it to NLS_OVERLOAD_OBJECT_TYPES_ONLY or NLS_OVERLOAD_ALL_TYPES depending on your requirements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →