wjmccann
wjmccann

Reputation: 522

Expanding anonymous function into a string

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

Answers (1)

gnovice
gnovice

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.
  • The input arguments to 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

Related Questions