File Exchange

image thumbnail

Functional Programming Constructs

version 1.2.0.1 (52.2 KB) by Tucker McClure
A set of files for treating many operations, like "if", "for", and even "()", as functions.

3 Downloads

Updated 01 Sep 2016

View Version History

View License

Editor's Note: This file was selected as MATLAB Central Pick of the Week

This set of files enables working more easily with functions of functions for concise and powerful code.
A few highlights of this way of working:

* Map multiple inputs to multiple functions and assign multiple outputs with a single line.

* Use conditionals (like "if"), loops, and recursion in anonymous functions.

A document with derivations and examples of these functions is included. See the readme.txt. Also, each function contains a 'help' entry with examples.

This submission requires strong familiarity with function handles and anonymous functions. If those aren't familiar terms, check the documentation first!

Cite As

Tucker McClure (2020). Functional Programming Constructs (https://www.mathworks.com/matlabcentral/fileexchange/39735-functional-programming-constructs), MATLAB Central File Exchange. Retrieved .

Comments and Ratings (25)

Tucker McClure

Thanks for pointing out an important subject, John!

John Smith

John Smith

OK, my bad - disregard the previous post.
Just RTFM'ed and found this alread is supported:
% Consider writing an anonymous function to safely normalize x.
% safe_normalize = @(x) iif(all(x == 0), @() x, ...
% true, @() x/sqrt(sum(x.^2)));
and
% To avoid calculating *all* possible conditions before determing which
% action to perform, the conditions can be expressed as anonymous functions
% to execute. These will be executed in order until a condition returning
% true is found. Here the same example as the above, with "@()" added
% before each condition to prevent its execution until it's needed (and
% skipping it altogether when it's not needed).

As I said - terryfic submission!

John Smith

Fantastic submission!
A problem I've encountered with iif() is that it does NOT employ lazy evaluation as if does. So you cannot pass in clauses that would throw an error even if their condition is NOT met. For example, say you want to evaluate:
if isempty(x), 0; else, x + 5; end
This wouldn't work with iif() as is because Matlab throws an error if x is empty because it tries to evaluate x + 5.
To work around this, it's possible to use anonymous functions in clauses which Matlab does NOT evaluate until needed. But then, you need to be able to pass arguments to these functions.
Here's what I ended up with:
iif = @(varargin) varargin{(find([varargin{1:3:end}], 1, 'first')-1)*3+2} ...
(varargin{(find([varargin{1:3:end}], 1, 'first')-1)*3+3}{:});

A usage example is:
iif(ifcond1, @(p1) clause1(p1), {x}, ...
elseifcond2, @(p1,p2) clause2(p1,p2), {x,y}, ...
elseifcond3, @(p1,p2,p3) clause3(p1,p2,p3), {x,y,z}, ...
true, @(p1) elseclause(p1), {x});

Hope this helps.
Comments / improvements are welcome.

Giuseppe Cascarino

Guido Previde Massara

Fantastic submission!

Tucker McClure

Thanks for the notes, John Smith. Yes, using : by itself stopped working somewhere between 13a and 15b, probably with the inclusion of the new execution engine for MATLAB (when MATLAB suddenly got way faster). The colon should be used as a string now, as in ':'.

Also, 'end' can't be used as an input; that's too low-level for MATLAB to interpret. Instead, you'll have to use length(...) or size(...).

Hope this helps!

John Smith

How do you make end work in paren?
E.g., paren(ones(1,10),end-3:end) gives:
Not enough input arguments.
Error in paren (line 34)
out = x(varargin{:});

And after checking, varargin is empty.

John Smith

If I don't quote : (as suggested by David), I'm getting Undefined function or variable 'paren'. This is on Matlab R2015b

Tucker McClure

Hi David and Joerg,

Thanks for the feedback. Those are good suggestions. Next time I update the code base, those will be in there.

- Tucker

David Szwer

Superb functions, that should really be part of main Matlab.

I'd suggest updating the documentation of "paren" with the following tip. The colon operator can be specified with or without quotes
(e.g. paren(magic(3), 2, :) and paren(magic(3), 2, ':')
are identical). However, if you use quotes you can also use ':' for linear indexing
(e.g. paren(magic(3), ':') )
but this does not work without quotes.

I would love to know what Matlab thinks the unquoted colon actually is, and why it sometimes doesn't work.

Joerg

Hi Tucker,

many thanks for the code. The "output" function is more flexibel if an optional argument is allowed:

function varargout = output(f, out_indices, varargin)

[outs{1:max(out_indices)}] = f(varargin{:});

varargout = outs(out_indices);

Felipe G. Nievinski

I guess instead of a separate iiff.m function this could be a patch for the original iif.m (backwards compatible):

isodd = @(x) logical(mod(x,2));
if isodd(numel(varargin))
varargin(end:end+1) = [{true}, varargin(end)];
end

Felipe G. Nievinski

% inline if-else (special case of general inline if-elseif):
% (dispense with typing true as the penultimate input argument)
function varargout = iiff(varargin)
varargin(end:end+1) = [{true}, varargin(end)];
[varargout{1:nargout}] = iif(varargin{:});
end

Felipe

output.m could reuse wrap.m, it seems:
function varargout = output(f, out_indices)
varargout = wrap(f, out_indices);
end

Felipe

Thanks for taking the time to comment, Tucker.

Tucker McClure

Hi Felipe,

You bring up a great point. There's no way to tweak |curly| so that it will expand all of the contents of a cell array as arguments into a function (or at least I haven't figured out a good way), because this is handled higher up by MATLAB, which is only looking for the form |f(x{:})|. Since |f(curly(x, ':'))| doesn't fit this form, it just won't work (the call to |curly(x, ':')| will only return the first output when passed directly into a function.

*However*, there are still ways to do the desired thing, such as your use of the |use| command; they just don't involve using |curly|. For instance, if |f| returns a cell array, and we want to call |f|, expanding all items from its cell array output into a new function, |g|, like this:

>> g(f(){:}) % <- generates a syntax error

we can do this instead:

>> use(f(), @(x) g(x{:}));

This says, "Run f() and pass its output to the next argument, which is a function handle. That function can now use that output (which we've called |x| here) however we want." Because |use| allows one to run a command and "use" its output multiple times without saving it to a variable, it's quickly useful in complex anonymous functions.

If this is a common pattern, you can also define:

>> expand_into_fcn = @(f, x) f(x{:});

And now we can use this expansion function to emulate what MATLAB does natively. E.g., to expand all elements in a cell array returned by |f| into the |cat| function, it's really quite compact:

>> expand_into_fcn(@cat, f())

This is exactly the same as:

>> x = f();
>> cat(x{:})

Let's go a step further. Let's take multiple outputs from a function and pass those into a new function. We'll need |wrap| here. We'll take both outputs from |min([5 2 3])|, |wrap| them into a cell array, and expand that array into the |cat| function to concatenate them along dimension 1.

>> use(wrap(@() min([5 2 3]), 1:2), @(x) cat(1, x{:}))
ans =
2
2

Hope this helps!

Felipe

I'm wondering if it's possible to harmonize the behavior of the curly.m function with that of the built-in {} in this case:
x = {'a' 'b'}, {x{:}}, {curly(x,':')}
x =
'a' 'b'
ans =
'a' 'b'
ans =
'a'
I don't think it's a problem with curly.m, as these anonymous functions also exhibit the same discord:
curlycolon = @(x) x{:}; {curlycolon(x)}
ans =
'a'
curlycolon = @(x) subsref(x, substruct('{}',{':'})); {curlycolon(x)}
ans =
'a'
I seems to be a specificity of the colon operator; from doc colon: "The colon notation can be used with a cell array to produce a comma-separated list. ... The comma-separated list syntax is valid inside () for function calls, [] for concatenation and function return arguments, and inside {} to produce a cell array."

I came across this problem trying to define this function:
subvec2ind = @(siz, subvecnum) use(num2cell(subvecnum), @(subveccell) sub2ind(siz, subveccell{:}));
subvec2ind([5 5 5], [1 2 3])
sub2ind([5 5 5], 1, 2, 3)

na ren

it's helpful!

Tucker McClure

Hey, thanks for all the good feedback everyone. I appreciate your taking the time to leave a rating!

Felipe G. Nievinski

YONGSUNG

good

Eric Sampson

Great work Tucker! :)

Ian

This is an excellent introduction to functional programming in Matlab, thanks Tucker!!!

Matthew

MATLAB Release Compatibility
Created with R2012b
Compatible with any release
Platform Compatibility
Windows macOS Linux

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!