Programatically creating an anonymous function that separates "Variables" and "Parameters"

8 views (last 30 days)
I have a function MYFUN with several inputs.
Say,
myfun = @(a,b,c,d) a*1000 + b*100 + c*10 + d
In actuality, some of these inputs are fixed “Parameters”, and others are the actual “Variables”, so I would like to have a function “Simplify”, such that
SIMPFUN = Simplify(FUN,PAR1,PAR2,..,PARN)
gets a function handle FUN and parameters PAR1, PAR2,... and returns a simplified function SIMPFUN, such that any non-empty PARs (which are my "Parameters") are substituted into the workspace of SIMPFUN and SIMPFUN only needs to get the empty PARS (which are the "Variables").
For example,
myfun = @(a,b,c,d) a*1000 + b*100 + c*10 + d ;
myfun(1,2,3,4)
ans =
1234
simp_myfun = Simplify(myfun,1,[],3,4) ;
simp_myfun(2)
ans =
1234
simp_myfun = Simplify(myfun,1,[],3,[]) ;
simp_myfun(2,4)
ans =
1234
simp_myfun(8,9)
ans =
1839
I have an implementation that works correctly, but is based on nested functions (cumbersome) and
I wonder if there is a more elegant solution based on anonymous functions.
Here is my current implementation:
function SimplifiedFcn = Simplify(Fcn,varargin)
isEmpty = cellfun(@isempty, varargin) ;
fEmp = find(isEmpty) ;
fnEmp = find(~isEmpty) ;
nonEmptyVars = varargin(fnEmp) ;
[~,k] = sort([fEmp fnEmp]) ;
SimplifiedFcn = @getfun ;
function Val = getfun(varargin)
Vars = [varargin, nonEmptyVars] ;
Val = Fcn(Vars{k}) ;
end
end

Accepted Answer

Guillaume
Guillaume on 6 Sep 2019
You won't be able to do this with just anonymous functions, they're too limited in matlab (no multiple statements, no branching).
This is more or less similar to what you have, only simpler and not using nested functions. In addition it allows for functions that return more than one output:
function f = Simplify(fcn, varargin)
inputloc = find(cellfun(@isempty, varargin));
inputpack = varargin;
f = @(varargin) insertinputs(fcn, inputpack, inputloc, varargin);
end
function varargout = insertinputs(fcn, inputpack, inputloc, newinputs)
inputdiff = {'Not enough', '', 'Too many'};
assert(numel(inputloc) == numel(newinputs), '%s input arguments to simplified function', inputdiff{sign(numel(newinputs)-numel(inputloc))+2});
inputpack(inputloc) = newinputs;
[varargout{1:nargout}] = fcn(inputpack{:});
end
  3 Comments
Stephen23
Stephen23 on 6 Sep 2019
"Is it possible to make the function return its correct number of nargin? "
No.
The nargin documentation states: "If the function includes varargin in its definition, then nargin returns the negative of the number of inputs", where varargin counts as one input.

Sign in to comment.

More Answers (2)

Guillaume
Guillaume on 6 Sep 2019
Edited: Guillaume on 6 Sep 2019
Thinking a bit more about it, metaprogramming in matlab can often only be achieved by using the dreaded eval. This is a rare case where there's no other way.
The following will work for any function regardless of the number of inputs/outputs, will give you the correct value for nargin, and only has the overhead of an anonymous function call when calling the resulting simplified function. The overhead of Simplify is of course not insignificant:
function f = Simplify(fcn, varargin) %#ok<INUSL> fcn is used inside eval
unboundidx = find(cellfun(@isempty, varargin));
boundinput = varargin; %#ok<NASGU> This is just a rename to avoid varargin appearing in the anonymous function (which could confuse the user). boundinput is used inside eval
unboundargs = compose('arg%d', unboundidx);
allargs = compose('boundinput{%d}', 1:numel(varargin));
allargs(unboundidx) = unboundargs;
%Note: you cannot use str2func in the following line as functions created by str2func are not closure (they do not capture the current workspace)
%so fcn and boundinput would not be visible. eval has to be used
f = eval(sprintf('@(%s) fcn(%s)', strjoin(unboundargs, ', '), strjoin(allargs, ', ')));
end
This is a much better solution than my original one, so I suggest you change the accepted answer.
Note that in the above I've created a variable called boundinput just the name varargin does not appear in the created anonymous function as it could confuse the user of the simplified function (the varargin would be the varargin captured by Simplify not a varargin of the anonymous function that is created)

Matt J
Matt J on 6 Sep 2019
Edited: Matt J on 6 Sep 2019
I just wanted to mention that a generic simplifier like what you have asked for will not normally give you the best performance. Normally, you have to write a simplifer that is customized to the original function. In the following continuation of your example, we customize based on the knowledge that your original function is linear, and that the simplified function should also be linear. For simplicity, I demonstrate assuming only 1 free parameter.
linearfun = @(a,b,c,d) a*1000 + b*100 + c*10 + d ;
function simp_fun = simplifyLinear(linearfun, varargin)
map=cellfun('isempty',varargin);
assert(nnz(map)==1 , 'In this simple example, only one free parameter is allowed')
argcell=varargin;
argcell{map}=0;
intercept=linearfun(argCell{:});
argcell=varargin;
[argcell{~map}]=deal(0);
argcell{map}=1;
slope=linearfun(argcell{:});
simp_fun=@(X) slope*X+intercept;
end
Notice that in this implementation, the resulting simp_fun will use only 1 addition/multiplication per call, unlike with the generic simplification which will always use 4 additions/multiplications.
  2 Comments
Guillaume
Guillaume on 6 Sep 2019
Yes, the generic simplifier has some overhead which can be significant if the function is called in a loop. It may also prevent some jit optimisation. However, it's a different concept than what Matt discusses. I think royk was looking for a generic way to bind an argument list to a function without any actual knowledge of the function implementation. Pure metaprogramming.
Matt solution relies on the knowledge of the function implementation, if you can do that, great! but it's no longer generic.
However, this talk of metaprogramming made me think of a better method. I'll post a new answer

Sign in to comment.

Categories

Find more on Function Creation in Help Center and File Exchange

Products


Release

R2019a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!