2

How to check whether a name is a locally accessible function and only locally accessible?

For example, with

function out = top_level_function(name)

% ???

    function helper2
    end
end

function helper1

end

Desired:

>> top_level_function('helper1')
>> ans =
     1
>> otherwise_valid_function(); % executes
>> top_level_function('otherwise_valid_function')
>> ans =
     0

isa(eval(['@',name]),'function_handle') with the necessary not file, not builtin check at the same time probably works in the case where there is no local name shadowing but I am also wondering if that can permit name to be evaluated as an expression without it being a function name. And also what to do when there is local name shadowing?


EDIT: My use case ended up looking like this:

msg=['value of ',name,' must be a valid local function name.'];
isIn=false;
try
    if isStringScalar(name) % placeholder for minimal input type check
        varargin{1}=char(name);
    end
    isIn=~isempty(which(name),'in',mfilename);
    msg=[msg,' Received ''',name,'''.'];
catch ME
end
if isIn
    out=eval(['@',name,';']);
else
    if ~exist('ME','var') || isempty(ME)
        error(msg)
    else
        fprintf(2,['%s: %s',newline], ME.identifier, ME.message);
        error(msg);
    end
end

It's made so that helper1, helper2 and whatever other helper coded in top_level_function has an automatic interface to be called and thus tested from outside.

12
  • @CrisLuengo: yes shadowing. edited. thx. So would a solution be to exclude results that can be made into a function_handle but have valid return with which(...,'all')? Commented Sep 23 at 17:19
  • @CrisLuengo: I just got a chance to read the answers. I want to know if helper1 is a local function defined in just the top_level_function in question. But if helper1 is globally available, I'd like to avoid it. My current use case is to output local functions as function_handle without creating the granular interface per top level function. The current purpose is for testing. Commented Sep 24 at 19:30
  • @CrisLuengo: top_level_function is returning as function handle. but i dont want to type a dictionary of local functions. i want it to just be able to output them as function_handle as long as the caller has the correct char vec for the function name, while also having some primitive safety that only function_handle created from local function can be returned. Commented Sep 24 at 22:57
  • @CrisLuengo: I see. I am curious of this question in itself too and it can be leveraged to solve my actual need. For my future reference, what should I do if I am in the same scenario again? Ask about my actual use case first? Ask both in separate question? Appreciate the advice. Commented Sep 25 at 3:04
  • Look, I learned a bunch from this question, I’m happy. But it was a bit frustrating not knowing exactly what you were asking. This is why you have three answers answering very different questions. Your pseudo-code labeled “desired” made no sense until you added the comment yesterday. The question itself is ambiguous too. It’s not that I want to know your actual use case, but I wanted the question to be well defined, so I know what a useful answer contains. Commented Sep 25 at 11:52

2 Answers 2

4

How to get a list of local functions

Within a function such as top_level_function you can use getcallinfo to get a full list of function names that are locally defined:

local_functions = getcallinfo([mfilename('fullpath'),'.m']);
local_functions_names = {local_functions(2:end).name};

The first output of getcallinfo is the main function of the file, which is why we skip it. So this gives us a list of all functions, local and nested.

The undocumented function getcallinfo gives lots if information about the local functions in a file (we'll use it again later in this answer). Note that, being undocumented, this function can change or disappear without notice. See Undocumented Matlab for more details. Type edit getcallinfo rather than help getcallinfo to read the non-public documentation.

How to tell if a local function shadows a global function

If you know that string or char vector name contains the name of a local function (it's one of the values in local_functions_names), then which(name, '-all') will return either a single element (the local function) or a longer list (the local function plus the global function plus any additional overloads). So simply counting how many values are returned is sufficient:

if numel(which(name, '-all')) == 1
   disp('This function only exists locally')
end

An alternative to see if a function is defined locally

Instead of comparing the name of the function against local_functions_names, you can follow Wolfie's implied solution and compare the output of which to that of mfilename:

path1 = which(name);
path2 = [mfilename('fullpath'),'.m'];
if strcmp(path1, path2)
   disp('This function is defined locally')
end

How to distinguish a local function from a nested function

(This is the original answer here, though it seems that it doesn't answer OP's question at all.)

The function getcallinfo can tell us if a function is nested or local.

Let's assume a file called so.m with the following code as an example:

function so()

    function nested_helper
    end
end

function local_helper
end

(This is similar to that in the OP, but with more explicit names.)

Starting off with this line from Wolfie's answer:

w = which('nested_helper', 'in', 'so');

We get w = '<path>/so.m'. We can now do:

m = getcallinfo(w);

This returns a struct m with three elements, one for each function in the file. We find the element corresponding to our function, and get its type:

m(strcmp('nested_helper', {m.name})).type

m(strcmp('local_helper', {m.name})).type

This returns the string (char array) 'nested-function' for nested_helper, and 'subfunction' for local_helper.

Another alternative to see if a function is defined locally, and whether it's nested or local

The internal function functions also gives us the desired information, but it works with a function handle, not a name. Within a function top_level_function, we can do

fun = eval(['@', name]);
m = functions(fun);
if strcmp(m.type, 'nested')
    disp('it is a nested function')
elseif strcmp(m.type, 'scopedfunction')
    disp('it is a local function')
else
    disp('it is not a local function')
    % note that this case also happens if `name` is not a valid function at all
end

Instead of eval(['@', name]) to create a function handle from the name of a function, you can use the function str2func. But str2func doesn't seem to be able to create a handle for a nested function; it does work with local functions.

Note that, being an internal function, functions might change. But it's been around for a long time and depended on for so many tools that this is not likely.

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

Comments

3

You can parse the output of which

Running which helper1 outside of top_level_function would give you

>> 'helper1' not found.

However, running within top_level_function for your examples gives

>> which( 'helper1' )
C:\Users\<username>\Desktop\top_level_function.m (helper1)  % Local function of top_level_function

>> which( 'helper2' )
C:\Users\<username>\Desktop\top_level_function.m (top_level_function/helper2)  % Nested function of top_level_function

While you can run w = which( ... ) to output the location, this won't include the helpful comment which tells you the local/nested state. You could use evalc to capture the output, but a better way would be to use the alternate syntax of which, from the docs:

str = which(fun1,'in',fun2) returns the path to function fun1 that is called by file fun2. Use this syntax to determine whether a local function is being called instead of a function on the path.

From anywhere (don't have to be within the calling function) you can run

>> w = which( 'helper1', 'in', 'top_level_function' )
w =
    'C:\Users\<username>\Desktop\top_level_function.m'

>> w = which( 'helper2', 'in', 'top_level_function' )
w =
    'C:\Users\<username>\Desktop\top_level_function.m'

>> w = which( 'helper3', 'in', 'top_level_function' )
w =
  0×0 empty char array

So if w is the full path the the top level function, then it is a local or nested function in that file. You can simply use strcmp versus the top level function path, which you could get dynamically using mfilename('fullpath') from within the top level function, or another call to which.

1 Comment

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.