Reputation: 522
I have a set of anonymous functions, and I want to convert them into strings. Normally I would just use func2str
, but the problem is I want the variables and internal functions to be expanded out into their "true" values. The problem I'm running into is that MATLAB is keeping these by the names, but recognizing the values. Example
classdef Bclass
properties
equation
end
function obj = Bclass(inEquation, inValue)
obj.equation = @(t,y) inEquation(t,y) * inValue;
end
function out = getStr(obj)
out = func2str(obj.equation);
end
end
The problem is that the func2str
call is outputting @(t,y) inEquation(t,y) * inValue
, when I actually want it to output something like @(t,y) t*y * 5
, if we had said b = Bclass(@(t,y) t*y, 5)
.
Is there a way to retrieve these variable values from MATLAB?
Upvotes: 2
Views: 459
Reputation: 125854
You can do this, but it could quickly become very difficult if your problem becomes any more complex than the example given above (i.e. more complicated anonymous functions, multiple nesting levels, etc.). You'll have to make use of the functions
function to get information on the function handle, and its behavior may change between releases. Additionally, you'll have to do quite a bit of string manipulation (using functions like regexp
, regexprep
, strsplit
, and strrep
, as I do below).
I've tried to include here the most general approach I could, allowing for the following possibilities:
inEquation
can be a non-anonymous function handle (i.e. @times
).inEquation
can simply be passed along as is without actually being invoked.inEquation
can be called multiple times in the anonymous function.inEquation
can be named differently than what it is invoked with in obj.equation
.obj.equation
can contain indexing operations.First, we'll initialize some variables to mimic your example:
f1 = @(m, n) m*n; % Note the different variable names, but it will still work
inEquation = f1;
inValue = 5;
f2 = @(t, y) inEquation(t, y)*inValue; % Function constructed using workspace variables
Next, we'll get the function information for f2
:
s = functions(f2);
varNames = fieldnames(s.workspace{1});
varValues = struct2cell(s.workspace{1});
out = s.function;
The workspace
field holds the variable names and values that were used to construct f2
, and the function
field is the string you'd get by calling func2str
on f2
. We'll also need to compute a few things so we can correctly parse opening and closing parentheses in f2
:
openIndex = (out == '(');
closeIndex = (out == ')');
parenIndex = cumsum(openIndex-[false closeIndex(1:end-1)]).*(openIndex | closeIndex);
Now, we'll loop over the workspace variables, convert their values to strings (if possible), and replace them in out
:
for iVar = 1:numel(varNames)
name = varNames{iVar};
value = varValues{iVar};
if isa(value, 'function_handle') % Workspace variable is a function handle
value = func2str(value);
callIndex = strfind(out, [name, '('])+numel(name);
fcnParts = regexp(value, '@\({1}([^\)])*\){1}(\S)*', 'once', 'tokens');
if isempty(callIndex) % Function handle is not invoked
if isempty(fcnParts) % Non-anonymous function handle (i.e. @times)
value = ['@' value];
end
out = strrep(out, name, value);
elseif isempty(fcnParts) % Invoked function handle (i.e. @times)
out = strrep(out, name, value);
else % Invoked anonymous function handle
for iCall = callIndex
args = out(iCall+(1:find(parenIndex(iCall+1:end) == parenIndex(iCall), 1)-1));
value = regexprep(fcnParts{2}, ...
strcat('(?<!\w)', strsplit(fcnParts{1}, ','), '(?!\w)'), ...
strsplit(args, ','));
out = strrep(out, [name, '(', args, ')'], value);
end
end
elseif isnumeric(value) && isscalar(value) % Workspace variable is a numeric scalar
out = strrep(out, name, num2str(value));
end
end
And we get the desired result for out
:
>> out
out =
@(t,y)t*y*5
Note that this will also work as expected with a non-anonymous function handle as well:
>> f1 = @times;
>> inEquation = f1;
>> inValue = 5;
>> f2 = @(t, y) inEquation(t, y)*inValue;
% Repeat above processing...
>> out
out =
@(t,y)times(t,y)*5
It will also work on some more complicated functions:
>> postVolt = @(g, V) -.05*g*(V+80);
>> preIdx = 5;
>> postIdx = 1;
>> index = 6;
>> obj.values = {};
>> f2 = @(t) postVolt(obj.values{preIdx}(index), obj.values{preIdx}(obj.voltIdx{postIdx}));
% Repeat above processing...
>> out
out =
@(t)-.05*obj.values{5}(6)*(obj.values{5}(obj.voltIdx{1})+80)
Upvotes: 5