Function definition meta-info

Last week, Loren Shure posted an article explaining some documented ways to retrieve information about the type of Matlab functions. Loren basically showed how we can use a combination of the built-in nargin and exist functions to check whether a specified function-name is a regular m-file, a script filename, a class name or something else.

Today I will discuss several additional alternatives for retrieving information about a specified function:

The mtree() alternative

Reader Mark Brown has commented about an alternative, using the semi-documented built-in function mtree:

>> t = mtree('mtree.m','-file'); t.select(1).kind  % m-class file
ans =
CLASSDEF
 
>> t = mtree('profile.m','-file'); t.select(1).kind  % regular m-file function
ans =
FUNCTION
 
>> t = mtree('test.m','-file'); t.select(1).kind  % script file
ans =
EXPR

mtree contains numerous other goodies in its reported parse-tree, including information about who-calls-what-where, set/used info about variables and other information used in the lexical parsing of the m-file. mtree is a very sophisticated m-file function (or rather, a class), but is very heavily documented internally, and so it can definitely be read and followed. Unfortunately, much of the internal processing is carried out by opaque internal c-functions (mtreemex), but there is still plenty of processing left for the 3K+ lines of m-code. I plan to write an extensive article about this function one day.

Unlike many other internal m-files, which are much simpler, less well written and sparsely documented (if at all), but for which personal credit is included in a comment, mtree includes no personal credit although it appears to be very well written and documented. I have to congratulate the unknown author(s) for both their proficiency and humility. Addendum: This unknown author is now confirmed to have been Steve Johnson, author of the mlint set of tools and functionality.

For people worried about future compatibility, note that mtree has existed on the Matlab path (%matlabroot%\toolbox\matlab\codetools\@mtree\mtree.m) since 2006 (I think R2006a, but I’m not certain; it already had version 1.3 by R2007b; the latest version [R2013b] is 2.5). Considering the large investment in its code thus far, and the significant effort that it would take to create a replacement, I don’t see this function going away in the near future. Then again, there is no certainty about this, and it might well disappear without notice in some future Matlab release.

The getcallinfo() alternative

getcallinfo is another semi-documented internal function, which uses mtree to construct an m-file’s call-tree and reports the results in either a tabulated manner (if no output arg is request), or a struct array (as output arg):

>> getcallinfo('profile.m');
Name           Type             Starts Ends Length Full Name           
----           ----             ------ ---- ------ ---------           
profile        function             1   242    242 profile             
ParseInputs    subfunction        247   354     82 profile>ParseInputs 
ParseOption    nested-function    262   287     26 profile>ParseInputs/ParseOption
notifyUI       subfunction        356   370     15 profile>notifyUI    
 
>> s = getcallinfo('profile.m')
s = 
1x4 struct array with fields:
    type
    name
    fullname
    functionPrefix
    calls
    firstline
    lastline
    linemask
 
>> s(4).calls
ans = 
      fcnCalls: [1x1 struct]
    innerCalls: [1x1 struct]
      dotCalls: [1x1 struct]
       atCalls: [1x1 struct]
 
>> s(4).calls.fcnCalls
ans = 
    names: {'usejava'}
    lines: 359
 
>> s(4).calls.innerCalls
ans = 
    names: {1x0 cell}
    lines: [1x0 double]
 
>> s(4).calls.dotCalls
ans = 
    names: {'com.mathworks.mde.profiler.Profiler.start'  [1x40 char]  [1x41 char]}
    lines: [363 365 367]

Note: In order to use getcallinfo, we first need to fix a small internal bug in %matlabroot%/toolbox/matlab/codetools/getcallinfo.m, specifically within the displayStructure() sub-function. Modifying this file may require administrator privileges, and can be done in any text editor. The bug is that the keyword length is used as a variable in this function (line #136 in R2013b), and so cannot be used to reference the built-in function length(). We can either rename the variable (lines 136, 139, 145) or the function. It’s easiest to rename length() to numel() in line #147 (highlighted below):

130:   function displayStructure(strc)
           ...
136:       length = getString(message('MATLAB:codetools:reports:RptLength'));137:       fullName = getString(message('MATLAB:codetools:reports:RptFullName'));
138:       
139:       fprintf('%-20s %-20s %-4s %-4s %-6s %-20s\n',name,type,starts,ends,length,fullName);
140:       fprintf('%-20s %-20s %-4s %-4s %-6s %-20s\n', ...
               ...
145:           getDashesForString(length), ...
146:           getDashesForString(fullName));
147:       for n = 1:numel(strc)   % original code: n = 1:length(strc)               ...
152:       end
153:   end

The mlint() alternative

A few months ago, I wrote about mlint‘s undocumented interface and ability to report much internal information about the analyzed file. It is no surprise that one of the undocumented mlint options is -calls, which lists the calling-tree of an m-file. For example:

>> mlint profile.m -calls
M0 1 14 profile
E0 242 3 profile
U1 122 15 callstats
U1 131 11 ParseInputs
U1 133 10 MException
U1 134 5 throw
U1 161 8 lower
U1 164 9 notifyUI
U1 165 23 true
U1 169 23 false
U1 180 9 profreport
U1 183 13 usejava
U1 184 13 error
U1 184 19 message
U1 188 21 profile
U1 189 16 isempty
U1 192 17 profview
U1 236 9 warning
S0 248 7 ParseInputs
E0 354 3 ParseInputs
U1 260 1 error
U1 260 7 nargchk
U1 260 17 Inf
U1 260 21 nargin
N1 262 23 ParseOption
E1 287 7 ParseOption
U2 263 12 strcmp
...

In this report, the first character represents the function type:

  • M = main (top-level) function
  • S = sub-function
  • N = nested function
  • U = out-of-scope (external/built-in) function
  • A = anonymous function
  • E = end-of-function indication

The following numbers indicate the nesting level, line #, column # and function identifier (name). In essence, it’s the same information presented by getcallinfo, with two distinctions: getcallinfo‘s report is much more nicely formatted, and on the other hand getcallinfo does not include internal function-calls (maybe there’s an undocumented switch that I haven’t found for this).

In this regard, I wish to once again praise Urs Schwartz’s excellent farg and fdep utilities, which use this undocumented mlint syntax. They seem to out-perform and out-class the built-in depends function by a wide margin…

The which() alternative

The built-in which function can be used to report the file-path of m-file functions, or an indicate that the function is built-in (i.e., coded as a C/C++ function in one of the Matlab libraries):

>> str = which('perfTest')
str =
C:\Yair\Books\MATLAB Performance Tuning\Code\perfTest.m
 
>> str = which('matlab.io.MatFile')
str =
C:\Program Files\Matlab\R2013b\toolbox\matlab\iofun\+matlab\+io\MatFile.m
 
>> str = which('sin')
str =
built-in (C:\Program Files\Matlab\R2013b\toolbox\matlab\elfun\@double\sin)
 
>> str = which('noSuchFunction')
str =
     ''

Note: a little-known option enables specifying sub-functions (although this does not work for nested functions for some unknown reason [bug? oversight?]):

>> str = which('nestedFuncName','in','MFileName');

The functions() alternative

Similar functionality can be achieved via the built-in functions, using function handles rather than function names:

>> functions(@perfTest)
ans = 
    function: 'perfTest'
        type: 'simple'
        file: 'C:\Yair\Books\MATLAB Performance Tuning\Code\perfTest.m'
 
>> functions(@matfile)
ans = 
    function: 'matfile'
        type: 'simple'
        file: 'C:\Program Files\Matlab\R2013b\toolbox\matlab\iofun\matfile.m'
 
>> fType = functions(@(a)a+1)
fType = 
     function: '@(a)a+1'
         type: 'anonymous'
         file: ''
    workspace: {[1x1 struct]}
 
>> functions(@transpose)
ans = 
    function: 'transpose'
        type: 'simple'
        file: ''

Unlike which, functions can also be used for both sub- and nested-functions:

% The following was called within the confines of a specific m-file function:
K>> fType = functions(@MFileName)
fType = 
    function: 'MFileName'
        type: 'simple'
        file: 'C:\Yair\MFileName.m'
 
K>> fType = functions(@subFunc)
fType = 
     function: 'subFunc'
         type: 'scopedfunction'
         file: 'C:\Yair\MFileName.m'
    parentage: {'subFunc'  'MFileName'}
 
K>> fType = functions(@nestedFunc)
fType = 
     function: 'MFileName/nestedFunc'
         type: 'nested'
         file: 'C:\Yair\MFileName.m'
    workspace: {[1x1 struct]}

Note that parentage and workspace are undocumented sub-fields of the returned struct: they are mentioned in the official doc page, but only in passing, without a format explanation. Also note that workspace is a cell array of a single element (contrary to the official doc – this is probably an internal bug), containing the actual workspace as a struct (fields = workspace variables). So it should be accessed as fType.workspace{1}.varName. Note that parentage and workspace are present only for certain types of function types.

Have you found any other nifty ways of retrieving function meta-info in run-time? Are you using such meta-info in an interesting manner? If so, please post a short comment below.

Categories: Medium risk of breaking in future versions, Semi-documented function, Stock Matlab function

Tags: , ,

Bookmark and SharePrint Print

6 Responses to Function definition meta-info

  1. Mark Brown says:

    Yair,

    I just posted on The Mathworks FileExchange, a function for finding file dependencies that uses MTREE. It should show up in a day or two as File ID# 43370. I would appreciate any feedback. Thanks.

    Mark Brown

  2. Some Guy says:

    I believe that this is the author of mtree():

    http://en.wikipedia.org/wiki/Stephen_C._Johnson

    • Thanks – I have independently verified this and updated the article text accordingly. I think that it’s a great loss to MathWorks and Matlab in general that Steve no longer works on Matlab.

  3. Pingback: Parsing mlint (Code Analyzer) output | Undocumented Matlab

  4. Another guy says:

    Is there any new/additional information available on the mtree function?

Leave a Reply


Your email address will not be published. Required fields are marked *