Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing and saving anonymous function in matlab

I would like a function (for example, a fit function) to return an anonymous function (usually stored in a struct) that I can save and use later. However, passing @func tends to pass a function pointer rather than the function itself. Is an inline function the only way to do this? I would like to avoid inline because it is extremely slow.

If that question is not clear, here is a sample of problematic code: I write a testFunc.m file in some PATH

    %testFunc.m
    function myfunc = testFunc()
        myfunc = @(x) x.^2;
    end

I then store the function in a struct. (I know this really should be an object!)

    >> mystruct = struct;
    >> mystruct.func = testFunc()
    >> mstruct.x = [1 2 3];
    >> save('myfile.mat','mystruct')
    >> mystruct.func(mystruct.x)

    ans = 

         1     4     9

If I then move myfile.mat or testFunc.m and load myfile.mat, I cannot load the old struct. Instead, I get the error:

    >> cd 'otherdir'
    >> load('../myfile.mat')

    Warning: Could not find appropriate function on path
    loading function handle PATH/testFunc.m>@(x)x.^2 

I know there is a problem because, if I check functions

    >> functions(mystruct.func)

    ans = 

         function: '@(x)x.^2'
             type: 'anonymous'
             file: 'PATH/testFunc.m'
        workspace: {2x1 cell}

Is there some way to strip off the file workspace information? Are inline functions the only solution?

like image 410
emarti Avatar asked Feb 14 '12 23:02

emarti


2 Answers

The Simple Case

If the functions you want to be anonymous are limited to being defined just in terms of their input parameters (like inline functions are), and you can commit to keeping one function on your path, then you can make "sanitized" anonymous functions.

function out = sanitized_anon_fcn(str)
out = eval(str);
end

So, in your code, where you want to make an anonymous function, do this.

%testFunc2.m
function myfunc = testFunc2()
    myfunc = sanitized_anon_fcn('@(x) x.^2');
end

As long as sanitized_anon_fcn.m stays on your path, you can delete testFunc2, and the saved function will continue to work. No special processing needed on save or load. Sanitized_anon_fcn basically works like inline but produces functions that are as fast as anonymous functions (because they are anonymous functions). The speed difference is about 10x in R2011b on my computer.

The General Case

In the general case, where the functions might actually use variables from their workspace, things get trickier.

Caveat: This is a bit of a sick hack, and I do not endorse its use in production code. But as an example of how the language works, I can't resist posting it.

I think you're 90% there already. But you need to preserve the workspace info instead of stripping it off, because it may contribute to the operation of the function. Instead of saving the anonymous function handle, grab the output of that functions() call you're making and save that.

fcn = testFunc();
fcn_info = functions(fcn);
save willbreak.mat fcn
save blah.mat fcn_info

Then load it back. You'll still get the same warning, but now the warning applies only to function handles captured inside the workspace of your top-level anonymous function. If your function doesn't actually reference them (and it shouldn't), you can ignore the warning and it'll work.

s0 = load('willbreak.mat')  % will warn and return unusable function
warning off MATLAB:dispatcher:UnresolvedFunctionHandle
s = load('blah.mat')  % will warn, but the first-level function will be usable
warning on MATLAB:dispatcher:UnresolvedFunctionHandle

Then pass it to something like this function which will bring your anonymous function back from the dead in a new workspace with the same workspace values, more or less.

function out = reconstruct_anon_fcn(s)

for iWks = 1:numel(s.workspace)
    wkspace = s.workspace{iWks};
    varnames = fieldnames(wkspace);
    for i = 1:numel(varnames)
        tmp = wkspace.(varnames{i});
        eval([varnames{i} ' = tmp;']);
    end
end

fcn_str = s.function;
fcn = eval(fcn_str);
out = fcn;
end

In our example case:

fcn = reconstruct_anon_fcn(s.fcn_info)
fcn(2)   % and it works!

Now, all the loaded anonymous functions will claim to be from this new file, but it shouldn't matter, because it's just the snapshotted state of the workspace, not closed-over variables, that is used by anonymous functions. And in the case where there were anonymous function handles in the workspace that actually were used by the computation, you'll get an appropriate error saying "Undefined function handle".

This is a hack, but maybe you could take this and extend it in to something reasonably robust.

like image 105
Andrew Janke Avatar answered Nov 07 '22 18:11

Andrew Janke


It is not possible, as it violates the concept of closure. Imagine that you define your anonymous function in that way:

   function f = LocalFunc()
       y = 3;
       f = @(x)(x+y);
   end

How can someone know about the fact that y was 3, besides saving the workspace info?

Edit(1) One might say that you can capture the value of the variable. But that is not always true. Imagine the following situation - You have a handle class, which has a method that adds to the input some property.

classdef Foo  < handle    
    properties
        DX;
    end

    methods
        function y = AddDX(this,x)
            y = x+ this.DX;
        end        
    end
end

Now you create a function that creates anonymous function that calls the method:

function fOut = TestConcept(handleClass)
    handleClass.DX = 3;    
    fOut = @(x)(handleClass.AddDX(x));
end

Now you create a new Foo() , and pass it to the TestConcept func:

 handleClass = Foo();
 fOut = TestConcept(handleClass);
 handleClass.DX = 3;
 fOut(4)

The result is 7

And then change the handle, and call it again:

 handleClass.DX = 100;
 fOut(4)

The result is 104. handleClass value could not have been saved, since it is not a value type, it is a handle.

As you can see, you cannot always capture the values, sometimes you have a reference.

like image 30
Andrey Rubshtein Avatar answered Nov 07 '22 17:11

Andrey Rubshtein