Anonymous functions and handles
Higher-order programming in MATLAB is built around the function_handle — a first-class reference to a function, named with a leading @. Function handles can be assigned to variables, stored in cell arrays, passed as arguments, returned from functions, and used as keys into containers. The handle is the substrate; the standard library provides higher-order combinators — arrayfun, cellfun, structfun, cellfun’s 'UniformOutput' mode — for the routine cases, and the Optimization Toolbox and the ODE suite are built entirely around accepting handles to user-supplied functions.
Named-function handles
The @ prefix produces a handle to a named function:
f = @sin;
f(pi/4) % 0.7071...
class(f) % 'function_handle'
The handle is bound at construction time to whichever function is on the path: it is essentially a closure over the function name. The handle remains valid across path changes (it captures the function itself, not the name).
g = @trapezoid;
g(@cos, 0, pi, 100) % handles are first-class arguments
Anonymous functions
The @(args) expr form constructs an anonymous function — an inline lambda whose body is a single expression:
sq = @(x) x.^2;
sq(5) % 25
sq([1 2 3]) % [1 4 9]
quadratic = @(a, b, c, x) a*x.^2 + b*x + c;
quadratic(1, -3, 2, 4) % 6
An anonymous function is, like every value in MATLAB, an immutable object. The body is restricted to a single expression; for multi-statement work, define a function (named, local, or nested).
Closures
An anonymous function captures the surrounding workspace’s variable bindings at the moment of construction:
n = 5;
add_n = @(x) x + n;
add_n(10) % 15
n = 100;
add_n(10) % still 15 — the capture was by value
The capture is by value — the closure carries its own copy of the variables. This is the simplest, least-surprising model: a closure cannot see subsequent changes to the variable. The mechanism is the cleanest way to partially apply a function:
function partial = bindFirst(f, x)
partial = @(varargin) f(x, varargin{:});
end
add5 = bindFirst(@plus, 5);
add5(3) % 8
For closures that need to modify shared state, the available mechanisms are nested functions (which capture by reference) and handle classes (see Classes and OOP).
Higher-order combinators
The standard library provides several common combinators:
| Combinator | Applies a function | To | Returns |
|---|---|---|---|
arrayfun(f, A) | element-wise | array | array of results |
cellfun(f, C) | element-wise | cell array | array (or cell, with 'UniformOutput', false) |
structfun(f, S) | per field | struct | array of results |
splitapply(f, X, G) | per group | grouped data | array of results |
accumarray(idx, val, [], f) | per accumulation | indexed values | array |
arrayfun(@(k) k^2, 1:5)
% [1 4 9 16 25]
cellfun(@strlength, ["alpha", "beta", "gamma"])
% [5 4 5]
cellfun(@(s) upper(s), {"alpha", "beta"}, 'UniformOutput', false)
% {'ALPHA', 'BETA'}
structfun(@(v) class(v), struct('a', 1, 'b', "x"), 'UniformOutput', false)
% struct with fields:
% a: 'double'
% b: 'string'
The crucial flag is 'UniformOutput'. When true (the default), every call to f is expected to return a scalar of identical class, and the results are gathered into a numeric or logical array. When false, the results are gathered into a cell array — necessary when f returns objects of varying shape or class.
The vectorised, broadcast-aware equivalents — f(x) over a numeric vector — are almost always faster than arrayfun(f, x) and should be preferred. arrayfun is the right tool when f is a scalar-only function that cannot be vectorised, or when the function is itself an anonymous lambda for which writing a vectorised form is more trouble than it is worth.
Functions as arguments
The most common use of handles is passing a function to a higher-order one. The Optimization and Differential-Equation libraries are built around this pattern:
% Root-finding: find x such that sin(x) = 1/2 near x=0.
x = fzero(@(x) sin(x) - 1/2, 0)
% 0.5236 (= pi/6)
% Unconstrained minimisation:
f = @(x) x(1)^2 + 100*(x(2) - x(1)^2)^2; % Rosenbrock
x = fminunc(f, [0; 0]);
% ODE: solve dy/dt = -2*y, y(0) = 1
[t, y] = ode45(@(t, y) -2*y, [0 5], 1);
plot(t, y)
The pattern is general: a numerical-library function takes a handle to the user’s model, evaluates it many times at points it chooses, and returns the result. The user supplies the mathematics; the library supplies the algorithm.
Composing functions
MATLAB has no compose operator, but composition is straightforward with an anonymous lambda:
compose = @(f, g) @(x) f(g(x));
fg = compose(@sin, @exp);
fg(0) % sin(exp(0)) = sin(1)
The pattern is occasionally useful but is rarely written explicitly in MATLAB code: in a vectorised expression, sin(exp(x)) is the same thing, written more clearly.
Partial application
The idiom for partial application is to build an anonymous function:
% Three forms of partial application of plus(x, y) = x + y
add5 = @(y) plus(5, y); % bind first
addy = @(x) plus(x, 5); % bind second (same value)
adder = @(x) @(y) plus(x, y); % curry
add5_curried = adder(5);
add5_curried(3) % 8
The currying form is rare in idiomatic MATLAB; the partial-binding form is common as a way to pass a “parameterised” function to a higher-order one.
Function handles in containers
Handles can be stored in cells and as struct fields, and the combination is the canonical way to dispatch by name:
ops = struct( ...
'add', @(x, y) x + y, ...
'subtract', @(x, y) x - y, ...
'multiply', @(x, y) x .* y, ...
'divide', @(x, y) x ./ y);
ops.add(2, 3) % 5
ops.multiply([1 2 3], 4) % [4 8 12]
% Programmatic:
choose = "multiply";
ops.(choose)([1 2 3], 4) % [4 8 12]
The pattern is the cleanest MATLAB substitute for a dictionary of functions or a polymorphic dispatch table. Since R2022b the dictionary class provides a similar facility with non-string keys.
Introspection
The function func2str(h) returns the source text of an anonymous function or the name of a named-function handle; the inverse str2func(s) converts a name to a handle. The function functions(h) returns a struct describing a handle — function name, file, type, captured workspace — and is the principal debugging tool when a handle behaves unexpectedly.
add_n = @(x) x + n;
functions(add_n)
% function_handle with values:
% function: '@(x)x+n'
% type: 'anonymous'
% file: '/path/to/caller.m'
% workspace: {1×1 struct}
The workspace field is the captured variables — a useful diagnostic when a closure produces the wrong answer because of an unexpected capture.