When can I pass a function handle?
Asked Answered
N

3

7

I have a function for cached evaluation. As one of the arguments, it takes a function handle. Under some circumstances, the function handle is unaccessible, and I don't quite understand why. The example below shows what got me stumped:

>> A.a = @plus; feval(@A.a, 1, 1)

ans =

     2

>> clear A
>> A.a.a = @plus; feval(@A.a.a, 1, 1)
Error using feval
Undefined function 'A.a.a' for input arguments of type 'double'.

So, if I have a function handle stored as a structure member, I can pass it along fine if it's one level deep, but not if it's two levels deep. In my real use case, I have a structure D that holds many (117) instances of various classes, so I actually have stct.obj.meth, where stct is a structure, obj is a class instance/object, and meth is a method. Passing @stct.obj.meth fails, but if I assign A = stct.obj, then passing @A.meth succeeds.

Under what conditions can I pass a function handle as an argument, so that it's still accessible down the stack?


Edit: Although in the use case above, I could simply remove the @ because @plus is already a function handle. However, consider the situation here:

>> type cltest.m

classdef cltest < handle
    methods
        function C = mymeth(self, a, b)
            C = a + b;
        end
    end
end

>> A.a = cltest();
>> feval(@A.a.mymeth, 1, 1)
Error using feval
Undefined function 'A.a.mymeth' for input arguments of type 'double'.
>> b = A.a;
>> feval(@b.mymeth, 1, 1)

ans =

     2

In this case, I need the @ before A.a.mymeth...

Nita answered 16/10, 2013 at 14:46 Comment(2)
I don't have a good explanation for why @A.a.mymeth does not produce a valid function handle, but it is worth noting that in your first example the extra @ is not needed. You could use A.a = @plus; feval(A.a,1,1); because A.a is already a function pointer.Fariss
@Fariss Yes. That was the gist of an earlier, now deleted, answer.Nita
L
5

Introducing classes was a big deal for MATLAB. So big, in fact, that they still do not work properly today. Your example shows that structure access and class method access conflict, because they had to overload the the meaning of dot '.' and didn't get it to work seamlessly. It all more or less works fine when you are calling class methods explicitly by their name on the MATLAB console, e.g. in your example >> A.a.mymeth(1,1). But when you have any type of indirection, it soon breaks.

You tried getting the function handle by >> @A.a.mymeth, which MATLAB cannot make sense of, probably because it gets confused by the mixed structure/class thing. Trying to work around using str2func doesn't work either. It works, again, only for explicit name access, as shown here. It breaks for your example, e.g. >> str2func('b.mymeth'). It does not even work inside the class. Try other indirections and watch them fail.

Additionally, MATLAB does not like giving you a class method's handles. There's no function for it. There's no way to get all function handles in one go, or even dynamically by a name string.

I see three options here. First, try changing your program, if possible. Do these functions need to sit in a classdef?

Second, follow your or nispio's workaround. They both create a temporary variable to hold a reference to the class instance in order to create a non-mixed access to its member methods. The problem is, they both require explicitly naming the function. You have to explicitly put this code for every function involved. No way to abstract that out.

Third, cheat by giving out your class' method handles from the inside. You can give them out in a structure.

classdef cltest < handle
    methods
        function C = mymeth(self, a, b)
            C = a + b;
        end
        function hs = funhandles(self)
            hs = struct('mymeth', @self.mymeth, ...
                        'mymeth2', @self.mymeth2);
        end
    end
end

You can then access the handles by name, even dynamically.

>> A.a = cltest;
>> feval(A.a.funhandles.mymeth, 1, 1);
>> feval(A.a.funhandles.('mymeth'), 1, 1)

ans =

     2

But be careful, by using this you can access Access=private methods from outside.

Leeth answered 17/10, 2013 at 11:30 Comment(0)
F
1

Try this:

feval(@(varargin)A.a.mymeth(varargin{:}),1,1);

It is a little kludgy, but it should work.

EDIT:

The way it works is by creating an Anonymous Function that takes a variable number of arguments, and dumps those arguments into the method A.a.mymeth(). So you are not actually passing a pointer to the function A.a.mymeth, you are passing a pointer to a function that calls A.a.mymeth.

An alternative way of achieving the same thing without using varargin would be:

feval(@(x,y)A.a.mymeth(x,y),1,1);

This creates an anonymous function that accepts two arguments, and passes them along to A.a.mymeth.

<speculation> I think that it must be inherent in the way that the unary function handle operator @ works. The Matlab parser probably looks at @token and decides whether token is a valid function. In the case of a.mymeth it is smart enough to decide that mymeth is a member of a, and then return the appropriate handle. However, when it sees A.a.mymeth it may discover that A is not a class, nor does A have a member named a.mymeth and therefore no valid function is found. This seems to be supported by the fact that this works:

A.a.a = @plus; feval(A.a.a,1,1)

and this doesn't:

A.a.a = @plus; feval(@A.a.a,1,1)

</speculation>

Fariss answered 16/10, 2013 at 19:54 Comment(2)
I don't think it's less kludgy than my own B=A.a; feval(@b.mymeth, 1, 1). The question is: what is going on here?Nita
You have to be careful with the workaround that you showed, because if you end up overwriting b before calling the feval, then you would have a problem. See my edit to the post for an explanation on why my solution works.Fariss
A
0

You can get around it by introducing a separate function that corrects what @ operator is not doing:

function h=g(f)
x = functions(f);
if ~strcmp(x.type, 'anonymous')
    h = evalin('caller', ['@(varargin)' x.function '(varargin{:})']);
else
    h = f;
end
end

Now for your example:

>> feval(g(@A.a.mymeth), 1, 1)
ans =
     2
>> feval(g(@b.mymeth), 1, 1)
ans =
     2

I think this will have the smallest impact on your code. You can make it a bit more elegant but less robust and/or readable. The uplus method is not defined for function_handle class so you can create uplus.m in folder @function_handle somewhere in your path with this content:

function h=uplus(f)
x = functions(f);
if ~strcmp(x.type, 'anonymous')
    h = evalin('caller', ['@(varargin)' x.function '(varargin{:})']);
else
    h = f;
end
end

Now you just need to use +@ instead of @. For your examples:

>> feval([email protected], 1, 1)
ans =
     2
>> feval([email protected], 1, 1)
ans =
     2
Anatola answered 18/10, 2013 at 13:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.