9

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?

3
  • I think I understand the point a bit better -- thanks for all the discussion! For the time being, I'm using inline functions, even though they are slow: %testFunc.m function myfunc = testFunc() myfunc = inline('x.^2'); end Commented Feb 15, 2012 at 0:58
  • Ah - if you just need functions that can be expressed as inlines, I think you can use "saniitized" anonymous functions from a common, clean workspace as a drop-in replacement for inline() and avoid the slowdown; see my updated answer. Commented Feb 15, 2012 at 15:35
  • Also, you can use addpath() to manage your path if you're not already. This lets you keep your source code on the Matlab path regardless of your current directory. Commented Feb 15, 2012 at 19:36

4 Answers 4

8

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Sorry I could not chat with you yesterday. I was kind of sleepy :) Anyway your hack looks fine, as long as you understand the limitations.
2

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.

9 Comments

Not quite true: anonymous functions are not closures; they capture the value in the variables at the time of the anonymous function construction. It is only named nested functions which are actually closures and capture live references to the variables in the enclosing workspace.
@AndrewJanke, you are right of course. But y could have been a handle, and you can't capture its value.
@AndrewJanke, please see the updated answer. I wrote a longer explanation. I would like to hear your opinion. thank you!
Ah right. And it's worse - y might have been a handle, but the output arg f is certainly a handle.
Well. That makes sense. But it doesn't mean you could never save an anonymous function handle with its workspaces - you just couldn't if they happened to contain a handle. The same argument applies to cells: in general, a cell array could contain a handle, but we can still save cells as long as the particular cell value we're saving doesn't. I think the same would apply to anonymous function handles with captured workspaces; it's the unfortunate case that the named output argument means that the workspace will always contain a handle to anonymous function.
|
1

I have encountered this problem before as well. str2func is almost identical to the handle operator (@) -- it does not create the file association, nor does it retain workspace variables. In your case:

myfunc = str2func('@(x) x.^2');

Try filing this behavior as a bug with the MathWorks, as it really undermines a very useful application of anonymous functions. If enough of us complain, they might fix it.

Comments

0

Expanding on the suggestion by @ArthurWard:

To save simple inline functions from your code environment, try

myStruct.myFunc = str2func(func2str( myFunc ));

save('myStruct.mat',myStruct);

func2str() turns your function into a literal string, and str2func() turns it back into a handle which matlab treats as free of dependencies.

You can tidy it up by making naming the operation something like

export_function = @(h) str2func(func2str( h ));

Any function that can survive func2str() should work.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.