Search code examples
matlaboctavechaining

Implementing 'curly' and 'access' "chaining" functions in matlab


I read this article on the mathworks blog about functional programming in matlab, and two of the helper functions there were:

paren = @(x, varargin) x(varargin{:});
curly = @(x, varargin) x{varargin{:}};

The obvious third one to complete the trio (and in keeping with the five-letter theme) would be:

acces = @(x, field) x.(field);

Putting the discussion of whether it's a good idea to implement chaining in this manner or not in matlab aside (note: octave supports chaining by default), paren seems to work well, as expected; however, curly and acces have a major drawback; consider the following code:

>> C = {1,2,3,4; 2,3,4,5; 3,4,5,6; 4,5,6,7};
>> A = [curly(C, 3, ':')]
A =
     3

i.e. the expected sequence generation didn't happen.
(note that this code works as expected in Octave, i.e. A = [3,4,5,6] )

Equally, acces does not produce a sequence in matlab

>> S = [struct('name', 'john'), struct('name', 'jim')];
>> A = {acces(S, 'name')}
A = 
    'john'

(whereas Octave produces the expected A = {'john', 'jim'} )

I understand that the difference is probably more a matter of implementation in terms of a. how functions return stuff in matlab vs octave, and/or b. how sequences are generated from cells and structs in the two languages.

However, is there a programmatic way to get matlab to perform the intended operation above?
In other words, is there a way to define curly and acces functions that return a sequence (extra bonus for anonymous function :p ) like octave does?


PS. The answer I'm looking for is not the trivial "to get multiple arguments out use varargout" one.
PS2. I tested this on Matlab 2013b, so I'm not aware if this behaviour has been "fixed" in later versions (though I highly doubt it). Tested on latest matlab online at http://matlab.mathworks.com


Solution

  • Disclaimer: Less of an answer and more of some random musings

    The issue here is that, in MATLAB, a single function (anonymous or otherwise) is incapable of returning a comma-separated list the way that dot referencing and {} indexing can.

    Even MATLAB's internal functions for performing the referencing are incapable of doing so:

    subsref(S, substruct('.', 'name'))
    %   john
    
    builtin('_dot', S, 'name')              % Only works pre-2015b
    %   jim
    
    subsref(C, substruct('{}', {3 ':'}))
    %   3
    
    builtin('_brace', C, 3, ':')            % Only works pre-2015b
    %   3
    

    But what a single function can do in MATLAB, is return multiple outputs. This is precisely how subsref and the other built-ins return the multiple values you're hoping for

    S = struct('name', {'john', 'jim'});
    
    [val1, val2] = subsref(S, substruct('.', 'name'));
    [val1, val2] = builtin('_dot', S, 'name');
    
    C = num2cell(magic(3));
    
    [val1, val2, val3] = subsref(C, substruct('{}', {3, ':'}));
    [val1, val2, val3] = builtin('_brace', C, 3, ':');
    

    Now this doesn't really help your helper anonymous function since it requires knowledge of how many outputs to expect and that in turn depends on the inputs.

    For your acces function, it's relatively straight forward to determine the number of outputs so you could easily do something like:

    [A{1:numel(S)}] = acces(S, 'name');
    

    Unfortunately, you can't do that inside of an anonymous function, and there's also no easy way to get a non-cell array out apart from wrapping this with a follow-up call to cell2mat

    [A{1:numel(S)}] = acces(S, 'name');
    A = cell2mat(A);
    

    You could create some anonymous functions to do these various operations, but they are messy.

    access_cell = @(s,n)arrayfun(@(x)acces(x,n), s, 'uniform', 0);
    access_array = @(s,n)arrayfun(@(x)acces(x,n), s, 'uniform', 1);
    

    As for your curly you could instead use paren to grab a subset of the cell array as a cell and then loop through it with cellfun to yield the result.

    % This is really just using parenthesis
    curly_sequence_cell = paren;
    
    curly_sequence_array = @(varargin)cell2mat(paren(varargin{:}));
    

    But the real solution is just use a temporary variable and then index into that using the typical MATLAB syntax :)

    S = struct('name', {'john', 'jim'});
    A = {S.name};