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.
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
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.
[…] much more detailed information about the nature of functions can be found using the semi-documented mtree function (or rather, Matlab class: %matlabroot%/toolbox/matlab/codetools/@mtree/mtree.m). This is a huge […]
Is there any new/additional information available on the mtree function?
Not to my knowledge, but you’re welcome to dig in the code and if you find anything useful please post a followup comment here.