« Earlier5 items total Later »

How to use nested functions to build an object oriented framework.

This currently only works in Matlab R14 SP3. In previous versions this does not work.

It is sometimes usefull to be able to obtain a function handle of a nested function in the calling scope. What this means is that you use evalin('caller',...) to perform an operation which retrieves function handles from the caller. An example of this is my unit test tool for Matlab MUnit which obtains a list of function handles whose names match the regular expression 'test_[A-Za-z0-9_]*'.

To do this first create a cell array of the function names you wish to grab. How you decide on what these names are are an application matter. In MUnit I scan the calling file and use regular expressions to find all function names that match the patterns.

In munit_testcase.m you will find

 stk = dbstack('-completenames');
 mname = stk(2).file;
 fcn_names = scan(mname, {'setup' ,'teardown', 'test_[A-Za-z0-9_]*'});



where the scan function is defined as

% SCAN
%
% Scans and extracts function names from an m file
% using regular expression patterns.
%
% Arguments
%   file        -   full path to an m file to scan
%   patterns    -   A cell array of regular expressions for function names
%                   to extract
%
function names = scan(fname, patterns)
    if iscell(patterns)
        patterns = sprintf('(%s)|',patterns{:});
    end
    str = evalc(sprintf('mlint(''-calls'',''%s'')', fname));
    names = regexp(str,'\d+\s*([AZa-z][A-Za-z0-9_]*)','tokens');
    names = cellfun(@(x) x{1}, names, 'uniformoutput', false);
    i = cellfun(@(x)~isempty(regexp(x, patterns)), names);
    names = names(i);
end

The above is different to what is in MUnit which uses an older scanning method and caching approach to avoid scanning the file more than once.

When you have your cell array, here stored in the variable fcn_names execute the following code

 fcns = evalin('caller',['@(){' sprintf('@%s ', fcn_names{:}) '}']);
 fcns = fcns();
 for i = 1:length(fcns);fcn = fcns{i};
   test_case.(fcn_names{i}) = fcn;
 end


If fcn_names contains

{'fun1' 'fun2' 'fun3'}


then the following expression will be evaluated in the caller

@(){ @fun1 @fun2 @fun3 }


That is it generates an anonymous function that when evaluated, line 2, generates a cell array of function handles.

Line 3 to line 5 just copies the function handles to named fields on a structure which can be returned.

You may ask why I need the extra layer of the anonymous function. I don't really know. It should be enough just to evaluate the cell array of function handles in the caller but this just does not work. Matlab bug or feature???

The savvy among you may realize that you can use this trick to easily create an object framework in Matlab. For example my theoretical object function may look like.

function self = my_object(x)
   self = create_object;
   function y = m_incr(r)
      x = x + r;
      y = x;
   end
   function y = m_decr(r)
      x = x - r;
      y = x;
   end
end


and create_object looks like
function self = create_object
 stk = dbstack('-completenames');
 mname = stk(2).file;
 fcn_names = scan(mname, {'m_[A-Za-z0-9_]*'});
 fcns = evalin('caller',['@(){' sprintf('@%s ', fcn_names{:}) '}']);
 fcns = fcns();
 for i = 1:length(fcns);fcn = fcns{i};
   self.(fcn_names{i}) = fcn;
 end
end


Now at the command line you should be able to

>> x = my_object(10);
>> x.m_incr(1)
ans =
      11
>> x.m_decr(2)
ans =
      9


Have fun

Fold an array using a function handle

Usage
>> a = { 'cow' 'cat' 'dog' };
>> fold(a,@(str, item) [ str  ',' item  ] )

ans =

cow,cat,dog

or
>> a = { 'cow' 'cat' 'dog' };
>> fold(a,@(str, item) [ str  ',' item  ],'' )

ans =

,cow,cat,dog

Implementation
function init = fold(arr, fh, init)
    if nargin == 2
        start = 2;
        if iscell(arr)
            init=arr{1};
        else
           init = arr(1);
        end
    else
        start = 1;
    end
    for i = start:length(arr)
        if iscell(arr)
            init = fh(init, arr{i});
        else
            init = fh(init, arr(i));
        end
    end
end

Search an array of items using your own comparison function

Usage
>> a = { 'cow' 'cat' 'dog' };
>> find_in_array(a,@(x)x(1)=='c')

ans =

    'cow'    'cat'


Implementation
function out = find_in_array(arr, fh)
    if ~iscell(arr)
        out = [];
        for i=1:length(arr)
            if fh(arr(i))
                out = [ out arr(i) ];
            end
        end
    else
        out = {};
        for i=1:length(arr)
            if fh(arr{i})
                out = { out{:} arr{i} };
            end
        end
    end
end

Perform an operation on each item of an array and collect the results

>> a = { 'cow' 'cat' 'dog' };
>> collect(a, @(x)fliplr(x))

ans =

    'woc'    'tac'    'god'


Implementation
function out = collect(arr, fh)
    out = cell(size(arr));
    for i = 1:length(arr)
        if iscell(arr)
            out{i} = fh(arr{i});
        else
            out{i} = fh(arr(i));
        end
    end
end

Perform an operation on each item of an array

>> a = { 'cow' 'cat' 'dog' };
>> each(a, @(x)disp(fliplr(x)))
woc
tac
god
>>


Implementation for each.m
function each(arr, fh)
    for i=1:length(arr)
        if iscell(arr)
            fh(arr{i});
        else
            fh(arr(i));
        end
    end
end

« Earlier5 items total Later »




Sponsored by

Sole Central

Your one stop shop for Birkenstock and Crocs shoes and sandles.