Memoize an anonymous function with externally scoped variables
Show older comments
I am trying to memoize a function that takes an anonymous handle with an externally-scoped parameter as input. The problem is that no two anonymous functions are ever the same. Therefore, every time the memoization handle mf is invoked, it executes in its entirety and freshly cache's the results.
mf=memoize(@subfunc);
A=3;
for i=1:4
mf(@(z)A*z)
end
I know that the Lord punisheth those who use eval(), but I can see no other alternative but to pass the anonymous function in char/string form and use evalin. This works fine when there are no externally-scoped variables. Caching indeed only occurs on the first call:
for i=1:4
mf('@(z)3*z')
end
However, when the anonymous function does contain an externally-scoped variable A, the workaround fails.
A=3;
mf('@(z)A*z')
So two questions,
- Why does the workaround fail to find externally scoped variables?
- Is there a more robust workaround?
function out = subfunc(argFun)
disp 'Caching'
if isa(argFun,'function_handle') %argFun is char or string
fun=argFun;
else
fun=evalin('caller',argFun);
end
out=fun(5);
end
Accepted Answer
More Answers (2)
1 Comment
Hi Matt,
Did you figure out exactly how the memoization determines if the function inputs are equal to inputs that have been cached?
The doc states:
"... MATLAB® returns the associated cached output values if the following conditions are true.
- The input arguments are numerically equal to cached inputs. When comparing input values, MATLAB treats NaNs as equal.
- The number of requested output arguments matches the number of cached outputs associated with the inputs."
Regarding 1, what does "numerically equal" mean if the inputs are not .... numerical?
I assumed that isequal would be called under the hood and so thought that a simpler container class might suffice (thought it uses the non-recommended functions):
classdef anonymous_function_container
properties
fun
end
methods
function obj = anonymous_function_container(fun)
obj.fun = fun;
end
function truefalse = isequal(anon_fun1,anon_fun2)
truefalse = isequal(functions(anon_fun1.fun),functions(anon_fun2.fun));
end
function out = function_eval(obj,val)
out = obj.fun(val);
end
end
end
With this class defintion we have:
>> f1 = @(z) A*z;
>> f2 = @(z) A*z;
>> isequal(f1,f2)
ans =
logical
0
>> isequal(anonymous_function_container(f1),anonymous_function_container(f2))
ans =
logical
1
So I thought that the following would work:
mf=memoize(@subfunc);
A=3;
for i=1:4
mf(anonymous_function_container(@(z) A*z))
end
function out = subfunc(argFun)
disp 'Caching'
out = argFun.function_eval(5);
end
Alas, the isequal method in the class was never called (I had a breakpoint in the debugger) and the output of the program was:
Caching
ans = 15
Caching
ans = 15
Caching
ans = 15
Caching
ans = 15
Seems like it's important to understand exactly how the memoization determines if the inputs are already cached, but I can't find any examples for anything other than numerical inputs, though I think other built-in types would typically apply like strings, structs, and cells, in addition to user-defined types. For the latter, what functionality needs to be implemented to meet the "is-already-cached" criterion?
h1 = @(z)A*z
h2 = @(z)A*z
isequal(h1, h2)
Each time you execute the building of the anonymous function, you end up with a different result. This has nothing to do with memoizing the result. To get around this you would need to use something like
A=3;
Az = @(z)A*z;
for i=1:4
mf(Az)
end
5 Comments
You are memoizing the call to subfunc. When the memoized function is presented with identical arguments, the cached value is to be returned. Different calls to building an anonymous function create different anonymous functions, so the memoized function is not being presented with identical arguments. The memoization engine is not supposed to do that for you.
This is not a failure of the memoization process; it is a limitation on the process of creating anonymous functions.
If we imagine that the process of anonymization must produce the same output handle given the same inputs, then we need to consider
global ABC
ABC = 123;
h1 = @(z)ABC*z
ABC = 456;
h2 = @(z)ABC*z
h1(1)
h2(1)
functions(h1).workspace{1}
functions(h2).workspace{1}
This establishes that the captured variable must lose its "global" status. If we were to "clear global" and then create a new ABC = 123 and h3 = @(z)ABC*z then should h1 be the same function handle as h3 ?
At the very least, for anonymous functions to return the same handles for the same inputs, it would be necessary to compare the contents of all captured variables.
format debug
ABC = 123
functions(h1).workspace{1}.ABC
we can see from this that a copy of the captured variables is made -- the pr is different between the two instances.
ABCD = struct('GHI', 789)
ABCD.GHI
h4 = @(z)ABCD.GHI*z
functions(h4).workspace{1}.ABCD
ans.GHI
We can see from this that it is a deep copy
I can't be bothered at the moment. That implies that the hypothetical mechanism to detect duplicate functionality would have to check the contents of possibly large variables -- since, after all, ABC(1e8) in one case might differ from ABC(1e8) in another case.
Walter Roberson
on 14 May 2026 at 7:36
Question:
mf=memoize(@subfunc);
for K = 1:3
A = randi(1);
f{K} = @(z) A*z;
mf(f{K})
end
We know through reasoning that A will be the same in each iteration. Should f{1} be isequal to f{2} ? Should mf(f{2}) invoke caching after mf(f{1}) has been executed?
How about
mf=memoize(@subfunc);
for K = 1:3
A = randi(2);
f{K} = @(z) A*z;
mf(f{K})
end
We known through reasoning that at least two of the f{:} will use the same A value. Should those f{:} be isequal() ? Should those mf(f{K}) invoke caching upon matching A ?
Matt J
on 14 May 2026 at 13:06
Categories
Find more on Performance and Memory in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!