Functional APIs in MATLAB with Self-Referential Structs and Closure Rebinding
A modern, functional approach to building extensible APIs in MATLAB without OOP boilerplate or nested function clutter.
By Arlon Arriola
Introduction
MATLAB isn't known for its flexibility in functional programming or recursive design patterns. But with a little creativity -- and an understanding of closures, struct behavior, and variable scope -- we can build a self-referential API structure that behaves like modern web or middleware architectures.
This pattern is simple, powerful, and surprisingly underused.
I'll walk through building a multi-level API using:
- Self-referential anonymous functions
- Structs with recursive function calls
- A rebinding loop that stabilizes closures across nested levels
The result? A PHP-style callable API object, built entirely in vanilla MATLAB.
Why Would You Want This?
You may want to:
- Build a modular, extensible toolbox without lots of m files
- Simulate middleware or route handlers
- Encapsulate logic in a structure that behaves like an object or service container
- Avoid OOP boilerplate when you don't need inheritance or properties
- Stay in a purely script/function-file domain (no classes)
This design mimics associative arrays + closures in PHP or object method chains in JavaScript -- using only struct
and @()
in MATLAB.
The Pattern: Closure-Stabilized API Struct
api = ''; % Predeclare (not strictly necessary, but good practice)
for i = 1:3
api = struct( ...
'a', struct( ...
'b', struct( ...
'f1', @(x) disp(['f1: ', x]) ...
), ...
'c', struct( ...
'f2', @(x) api.a.b.f1(['f2 (-> f1): ', x]) ...
), ...
'd', struct( ...
'f3', @(x) api.a.c.f2(['f3 (-> f2 -> f1): ', x]) ...
) ...
) ...
);
end
Why the for
loop?
Because MATLAB closures capture by reference, not by value, we need to rebuild the full struct several times to stabilize all nested references.
Each pass:
- Rebinds
api
to include more complete versions of itself - Updates all function handles to point to the final, stable API
We only need as many iterations as the deepest level of self-reference, so for i=1:3
handles 3 levels (f3 -> f2 -> f1
). You can always set it to 10 or more and never worry.
Usage
api.a.b.f1('Hello World'); % Direct call
api.a.c.f2('Hello World'); % f2 calls f1 internally
api.a.d.f3('Hello World'); % f3 -> f2 -> f1
Output:
f1: Hello World
f1: f2 (-> f1): Hello World
f1: f2 (-> f1): f3 (-> f2 -> f1): Hello World
Each level wraps the previous one -- a perfect model for chaining behavior or composing logic.
Design Inspiration
This approach mirrors functional paradigms:
Concept | Equivalent |
---|---|
api.a.b.f1() | PHP-style associative arrays with callable values |
Recursive struct rebinding | Fixed-point combinator / Y-combinator |
@() closures | Anonymous functions with lexical scoping |
Chainable API | Middleware stacks, command patterns, services |
It's like building a small internal DSL (domain-specific language) for your code.
Use Cases
- Command router for a CLI toolbox
- GUI event handler API (
api.ui.buttons.save.click(...)
) - Dependency injection container
- Composable simulations (
api.sim.engine.step(...)
) - HTTP-style route dispatchers (
api.http.get.users(...)
)
You could even expand this with:
- Auto-generated docs
- Runtime introspection
- Dynamic module loading
- Middleware-like pipelines
Purpose
This tiny idiom -- rebuilding a struct of self-referential anonymous functions -- opens a door to functional design in MATLAB without the rigidity of OOP or class definitions.
The simplicity is deceptive. It gives you:
- Namespacing
- Closure binding
- Call chaining
- Function encapsulation
All in one object.
Bonus: Expandability Template
for i = 1:10 % deeper loop = deeper nesting
api = struct( ...
'layer1', struct( ...
'f', @(x) disp(x) ...
), ...
'layer2', struct( ...
'f', @(x) api.layer1.f(['-> ', x]) ...
), ...
'layer3', struct( ...
'f', @(x) api.layer2.f(['-> ', x]) ...
) ...
);
end
Functional Multi-Line Anonymous Functions via Recursive Self-Reference
MATLAB famously limits anonymous functions to a single expression -- no sequential logic, no if/else, no multiple statements. But that restriction can be bypassed using recursive functional programming and state threading.
The Core Trick
- Start with a recursive anonymous function
- Use a branching function (like a custom ternary or an
if_
) - Mutate parameters via new struct on each pass
- Final recursion returns result
Method 1: if_ Branching with Recursion
if_ = @( pred_, cond_ ) cond_{ 2 - pred_ }(); % Choose branch
makeZ = @(makeZ, x) ...
if_(~isfield(x, 'i'), ...
{@() makeZ(makeZ, struct('i', 1, 'x', x, 'z', zeros(5))), ...
@() if_(x.i == 1, ...
{@() makeZ(makeZ, struct('i', 0, 'x', x.x, ...
'z', [x.x(1)*cos(x.x(2)) x.z(1,2:5); x.z(2:5,:)]))}, ...
{@() [x.z(1:2,:); x.z(3,1:3) log(x.x(3)) x.z(3,5); x.z(4:5,:)]}) ...
});
mkZ = @(x) makeZ(makeZ, x);
mkZ([2 3 4]);
Method 2: iif and recur Pattern
iif = @(varargin) varargin{2 * find([varargin{1:2:end}], 1, 'first')}();
recur = @(f, varargin) f(f, varargin{:});
makeZ_2 = @(x) recur(@(f, k) ...
iif(~isfield(k,'i'), ...
@() f(f, struct('i',1,'x',k,'z',zeros(5))), ...
(isfield(k,'i') && k.i==1), ...
@() f(f, struct('i',0,'x',k.x,'z',[k.x(1)*cos(k.x(2)) k.z(1,2:5); k.z(2:5,:)])), ...
true, ...
@() [k.z(1:2,:); k.z(3,1:3) log(k.x(3)) k.z(3,5); k.z(4:5,:)]) ...
, x);
makeZ_2([2 3 4]);
Method 3: Custom ternary
Operator
ternary = @(varargin) varargin{length(varargin) - varargin{1}};
makeZ_3 = @(makeZ_3, x) ...
feval(ternary(~isfield(x,'i'), ...
@() makeZ_3(makeZ_3, struct('i', 1, 'x', x, 'z', zeros(5))), ...
@() feval(ternary(x.i==1, ...
@() makeZ_3(makeZ_3, struct('i', 0, 'x', x.x, ...
'z', [x.x(1)*cos(x.x(2)) x.z(1,2:5); x.z(2:5,:)])), ...
@() [x.z(1:2,:); x.z(3,1:3) log(x.x(3)) x.z(3,5); x.z(4:5,:)]) ...
));
mkZ_3 = @(x) makeZ_3(makeZ_3, x);
mkZ_3([2 3 4]);
How This Works
- State is threaded through each recursive call
- Branching logic decides which transformation to apply
- The function emulates a multi-line block
Why This Matters
This isn't just a MATLAB trick. It's a functional programming pattern, adapted to a language that doesn't quite support it. These methods:
- Enable multi-line logic in inline definitions
- Keep all code within a closure, no external state
- Are reusable, composable, and embeddable in factory patterns
Also see: Is it possible to write several statements into an anonymous function?
Postscript: Naming the Pattern
Internally, I've started calling this structure the Closure-Stabilized API Blaster — a nod to both its recursive closure rebinding pattern and its power as a functional API generator in plain MATLAB. It's part of the broader Blaster family: minimal, modular tools that skip the boilerplate and go straight to the logic.
You can think of it as a small internal DSL builder, middleware router, or a MATLAB-native service container — all without writing a single classdef file.