Loren on the Art of MATLAB

Introduction to Functional Programming with Anonymous Functions, Part 2 14

Posted by Loren Shure,

Tucker McClure is an Application Engineer with The MathWorks. He spends his time helping our customers accelerate their work with the right tools and problem-solving techniques. Today, he'll be discussing how "functional programming" can help create brief and powerful MATLAB code.

Contents

Recap

For Part 1, click here.

Last time, we said that functional programming was marked by storing functions as variables (function handles) and working with functions that act on other functions. We put these ideas together to implement our own version of a map function for handling multiple inputs and outputs from multiple functions simultaneously, and we created iif, an "inline if", to allow the use of conditional statements inside of anonymous functions. So how might we work with recursive functions -- functions of themselves? We'll see how a functional programming style allows us to implement recursive functionality inside anonymous functions, and this will pave the way for the final part, in which we'll implement loops, without ever using for or while (which we can't use in anonymous functions).

Before we get started, let's implement iif again; we're going to need it frequently.

iif = @(varargin) varargin{2*find([varargin{1:2:end}], 1, 'first')}();

Anonymous Function Recursion

Recall that a recursive function is a function that calls itself. It therefore needs some way to refer to itself. When we write an anonymous function, it isn't "named" (hence, "anonymous"), so it can't call itself by name. How can we get around this?

Let's start with a Fibonacci sequence example. Recall that the nth number of the Fibonacci sequence is the sum of the previous two numbers, starting with 1 and 1, yielding 1, 1, 2, 3, 5, 8, 13, 21, etc. This is easy to implement recursively.

   fib = @(n) iif(n <= 2, 1, ...                    % First two numbers
                  true,   @() fib(n-1) + fib(n-2)); % All later numbers

But hey, that can't work! We haven't defined fib yet, so how could this anonymous function call it? In fact, the anonymous function will never "know" we're referring to it as fib, so this won't work at all. Therefore, instead of trying to call fib directly, let's provide another input: the handle of a function to call, f.

fib = @(f, n) iif(n <= 2, 1, ...                      % First two numbers
                  true,   @() f(f, n-1) + f(f, n-2)); % All later numbers

Getting closer. Now, if we pass fib to fib along with the number we want, it will call fib, passing in fib as the first argument, recursively until we get our answer.

fib(fib, 6)
ans =
     8

Ok, that's right. The sixth number of the sequence is 8. On the other hand, the syntax we've created is terrible. We have to provide the function to itself? I'd rather not. Instead, let's just write a new function that hands fib to fib along with the input n.

fib2 = @(n) fib(fib, n);

fib2(4)
fib2(5)
fib2(6)
ans =
     3
ans =
     5
ans =
     8

That's a lot closer to what we want, but there's one more step. Let's write a function called recur to hand a function handle to itself, along with any other arguments. This makes recursion less cumbersome.

recur = @(f, varargin) f(f, varargin{:});

That was simple, so now let's re-write fib. The first argument to recur is the function, which we'll define inline. The second is n. That's all there is to it. It now reads as "Recursively call a function that, if k <= 2, returns one, and otherwise returns the recursive function of k-1 plus that of k-2, starting with the user's input n." (If it doesn't read quite this clearly at first, that's ok. It takes some getting used to. Comment liberally if necessary!)

fib = @(n) recur(@(f, k) iif(k <= 2, 1, ...
                             true,   @() f(f, k-1) + f(f, k-2)), ...
                 n);

And we can find the first ten numbers of the sequence via arrayfun.

arrayfun(fib, 1:10)
ans =
     1     1     2     3     5     8    13    21    34    55

Factorial (f(n) = 1 * 2 * 3 * ... n) is another easy operation to represent recursively.

factorial = @(n) recur(@(f, k) iif(k == 0, 1, ...
                                   true,   @() k * f(f, k-1)), n);
arrayfun(factorial, 1:7)
ans =
  Columns 1 through 6
           1           2           6          24         120         720
  Column 7
        5040

A number to an integer power has a nearly identical form. Here's 4.^(0:5).

pow = @(x, n) recur(@(f, k) iif(k == 0, 1, ...
                                true,   @() x * f(f, k-1)), n);
arrayfun(@(n) pow(4, n), 0:5)
ans =
           1           4          16          64         256        1024

That was a big step for anonymous functions, using both recursion and an inline conditional together with ease. Like map and iif, recur, looks strange at first, but once it's been seen, it's hard to forget how it works (just make one of the inputs a function handle and pass it to itself). And recursion doesn't have to stop at interesting mathematical sequences of numbers. For instance, in the next part, we'll use this to implement loops in, but first, we'll need a some helper functions and a good way to execute multiple statements in an anonymous function.

Helpers

These little functions are useful in many circumstances, and we're going to need curly frequently.

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

They allow us to write x(3, 4) as paren(x, 3, 4) and similarly for curly braces. That is, now we can think of parentheses and curly braces as functions! At first this might not seem useful. However, imagine writing a function to return the width and height of the screen. The data we need is available from this call:

get(0, 'ScreenSize')
ans =
           1           1        1920        1200

However, we don't need those preceeding ones. We could save the output to a variable, say x, and then access x(3:4), but if we need this in an anonymous function, we can't save to a variable. How do we access just elements 3 and 4? There are numerous ways, but paren and curly are similar to constructs found in other languages and are easy to use, so we'll use those here.

Now we can write our screen_size function to return just the data we want.

screen_size = @() paren(get(0, 'ScreenSize'), 3:4);

screen_size()
ans =
        1920        1200

While on the subject, note that we can actually use any number of indices or even ':'.

magic(3)
paren(magic(3), 1:2, 2:3)
paren(magic(3), 1:2, :)
ans =
     8     1     6
     3     5     7
     4     9     2
ans =
     1     6
     5     7
ans =
     8     1     6
     3     5     7

We do the same with the curly braces. Here, the regular expression pattern will match both 'rain' and 'Spain', but we'll only select the second match.

spain = curly(regexp('The rain in Spain....', '\s(\S+ain)', 'tokens'), 2)
spain = 
    'Spain'

(Click for Regexp help.)

It also works with ':' (note that the single quotes are required).

[a, b] = curly({'the_letter_a', 'the_letter_b'}, ':')
a =
the_letter_a
b =
the_letter_b

Executing Multiple Statements

With curly in place, let's examine something a little different. Consider the following:

do_three_things = @() {fprintf('This is the first thing.\n'), ...
                       fprintf('This is the second thing.\n'), ...
                       max(eig(magic(3)))};

do_three_things()
This is the first thing.
This is the second thing.
ans = 
    [25]    [26]    [15]

We've executed three statements on a single line. All of the outputs are stored in the cell array, so we have three elements in the cell array. The first two outputs are actually garbage as far as we're concerned (they're just the outputs from fprintf, which is the number of bytes written, which we don't care about at all). The last output is from max(eig(magic(3))); That is, the biggest eigenvalue of magic(3) is exactly 15. Let's say we just wanted that final value, the eigenvalue. It's the third element of the cell array, so we can grab it with curly.

do_three_things = @() curly({fprintf('This is the first thing.\n'), ...
                             fprintf('This is the second thing.\n'), ...
                             max(eig(magic(3)))}, 3);

do_three_things()
This is the first thing.
This is the second thing.
ans =
           15

For a more complex example, let's say we want to write a function to:

  1. Create a small figure in the middle of the screen
  2. Plot some random points
  3. Return the handles of the figure and the plot

Then by storing all of the outputs in a cell array and using curly to access the outputs we care about, we can make a multi-line function with multiple outputs, all in a simple anonymous function.

dots = @() curly({...
    figure('Position', [0.5*screen_size() - [100 50], 200, 100], ...
           'MenuBar',  'none'), ...                % Position the figure
    plot(randn(1, 100), randn(1, 100), '.')}, ...  % Plot random points
    ':');                                          % Return everything

[h_figure, h_dots] = dots()
h_figure =
     3
h_dots =
          187

(As a quick aside, note that if a statement doesn't return anything, we can't put it in a cell array, and so we can't use it this way. There are ways around this, discussed here.)

To Be Continued

Today, we've come a long way, from a simple condition through recursion and executing multiple statements. Here's a roundup of the functions so far.

map     = @(val, fcns) cellfun(@(f) f(val{:}), fcns);
mapc    = @(val, fcns) cellfun(@(f) f(val{:}), fcns, 'UniformOutput', 0);
iif     = @(varargin) varargin{2*find([varargin{1:2:end}], 1, 'first')}();
recur   = @(f, varargin) f(f, varargin{:});
paren   = @(x, varargin) x(varargin{:});
curly   = @(x, varargin) x{varargin{:}};

These can also be found here, implemented as regular MATLAB functions that can be kept on the path.

Next time, we'll look at loops. Until then, have you worked with functions such as paren or curly? How else are people implementing these or similar operations? Let us know here.


Get the MATLAB code

Published with MATLAB® R2012b

14 CommentsOldest to Newest

I’m still not clear on why these anonymous functions would be useful…

So far, the examples only show things that could easily have been done via subfunctions. Those subfunctions can easily be converted to ‘proper’ functions if they turn out to be generally useful and then would be no harder to read than regular Matlab.

Most of my coworkers are less familiar with Matlab than I am and would have no idea what was happening if they saw an anonymous function. I only need to write my code once, but it may be read (and need to be maintained) by many people, many times and for a long period of time. Therefore, it would be foolish for me to use these anonymous functions for code that is to be shared, even if it saved me time in writing the code (which, so far, it would not).

For code for one-time use, where I will be the only user, the considerations above do not apply. For these, I would generally create a simple utility function. Depending on whether it’s project-specific, I’ll put it either in my utilities folder (which is on my path by default) or in the working directory (which holds the project). Either way, I will be able to re-use it.

As for the parens and curly functions, I think most Matlab users have long wished for these… which is all the more reason to make them available publicly (m-file in a shared location, if Matlab itself doesn’t implement them) instead of ‘hiding’ them in an anonymous function.

(By the way, they don’t always work: paren(unique({‘a’,’c’,’a’}),3) results in an error because by default, unique only returns a single output. So it’s back to the tried-and-true method of an extra line of code and dummy variables (or ~).)

All in all, I really don’t see how anonymous functions fit into my Matlab usage. For now (and for my purposes), they appear to be just a gimmick. Am I missing something?

I don’t think you’re missing anything LyVe. I’d just say that this is a discussion of the functional programming style, and using anonymous functions here makes it fairly easy to demonstrate since it doesn’t rely on external files. In fact, everything we’ve shown so far (including paren and curly) is implemented as a “regular” function in the File Exchange entry.

In scripts, you can’t write a subfunction, so I use anonymous functions very heavily in scripts and for simple wrapper functions. Many users haven’t seen anonymous functions, but a few minutes of explanation are all that’s necessary, and then they’re just that much more versatile in MATLAB.

There are some other things in the File Exchange entry too, such as the ‘output’ function. For instance, for your paren(unique(…), 3) example, I think the point is to capture the third output? This would do it:

output(@() unique({‘a’,’c’,’a’}), 3)

I use this one all the time.

So there’s no best way to program in MATLAB, just like there’s no best way to cook. Nonetheless, sometimes knowledge of an exotic or rare technique can pull a dish together elegantly and powerfully. The idea here is only to demonstrate the techniques; the dishes are up to you!

@LyVe,

You may also want to comment on other posts that are more on anonymous functions, rather than functional programming (which this post is about).

Here’s one:
http://blogs.mathworks.com/loren/2012/08/29/thoughts-about-anonymous-functions/

You’re not alone in your take on anonymous functions. I’ve found them to be quite useful, but as Tucker mentioned, this is just one of many programming techniques that are available in MATLAB, and you can do a lot of things with using only a few of the techniques.

Also, in response to your point about the re-usability and usefulness of anonymous functions for novice MATLAB users, I use anonymous functions within my functions. Those are just for my use, and not for others to use. I don’t give anonymous functions to others. They help me in developing my utility functions, and therefore, I don’t really intend my end-users to be using any of the anonymous functions I create/use.

I don’t know that I’ll use these constructs, but they’re certainly interesting. Especially paren and curly, which would be great to build into the core MATLAB language.

One question: why can paren take : directly, but curly needs it as a char, ‘:’?

Hi Jacob,

This is a question I had as well, so I asked the development folks. This apparently gets right at the core of the way MATLAB interprets things, and I’m not sure I completely understand where : works as opposed to ‘:’. After some discussion with them, it seems ‘:’ works generally whereas : does not. That is, you can use ‘:’ with paren as in:

paren(magic(3), ‘:’, 2)

So I’d stick with ‘:’.

Hi Tucker, I find that many times I only need a single ‘if else’ statement, so instead of iif I have defined a ternary/conditional operator like C has:

cop = @(varargin) varargin{3-varargin{1}}();

This helps keep the syntax a little cleaner, although I can’t line up the spacing here like you did:

fib = @(n) recur(@(f,k) cop(k <=2, 1, (@() f(f, k-1) + f(f, k-2))),n)

The COP function is also handy for doing things like:

out = cop(test, @()doIfTrue(in), @doIfFalse);

If you find it useful too, perhaps you could add it to your FileExchange toolbox :)

Also, re the screen_size example, I think you can just use paren directly if you only need to do this operation once or twice, correct?

screen_size_var = paren(get(0, ‘ScreenSize’), 3:4);

Hi Eric,

Great comments. Thanks for bringing up the ternary operator as a simple anonymous function. I would add it to the File Exchange entry, but there’s actually already a ‘tern’ operator included! :) Let me know if you spot some other things it could use!

Also, yes, you’re exactly correct about the screen size. The version above is a function to return the screen size, and yours is the variable version. I use ‘paren’ all the time for exactly what you suggest.

Glad to see someone already thinking in functional terms. Please share any other tips with us!

Hi Tucker, I hadn’t looked through your FileEx entry when I was reading the posts – oops :)

A couple more thoughts/questions…

- How would this series of posts changed if there was a built-in ternary operator in MATLAB, since that would have allowed conditional statements inside anonymous functions?

- Is there a way to get END to work as an argument for paren/curly? BTW, I also vote that these two things should be available as built-in syntax. It would help satisfy people’s desire to be able to index function calls directly, for example.

- Have you found a use for closures aka returned handles to nested functions?

- Does MATLAB support tail-call optimization / tail-recursion elimination (http://c2.com/cgi/wiki?TailCallOptimization)

You might like this blog, http://apageofinsanity.wordpress.com/2011/12/

Hi Eric,

If MATLAB had a built-in ternary operator, I’m not sure how these posts would change. I hope the idea of functions of functions would still have been interesting, but ‘iif’ certainly wouldn’t have as much appeal!

I don’t know of any way to get ‘end’ to work as an argument. I’ve just used length(x), size(x, …) or numel(x) in general.

Using handles to nested functions or even subfunctions is actually fairly common. Ok, it’s been done anyway. I do this. Loren does this. Suppose you pass some arguments to a function that configures some data and chooses a method based on the inputs. Let’s call it f = get_filter(). After configuring things, this might return a handle to a nested function or subfunction that is might to do exactly the type of filtering you configured up front. No more need to run through the configuration steps on each call to the function. You only call the part you need now (by using whatever handle was returned from get_filter()). This works fairly well. However, object-oriented programming is probably a better match here.

As for tail-call optimization, I would expect not. Besides, if it’s not in the doc, I wouldn’t count on it. :)

That blog looks interesting. A y-combinator in MATLAB? Hehe. Nice.

Thanks Gerrit and Steve. I guess my ‘paren’ function squashes the help discussion for the ways to use parentheses. Oops. I’m going to guess though that users trying out these techniques are already well aware of the ways to use parentheses in MATLAB! So, hopefully, there’s no harm here. It’s not clear if it’s useful to change this function name going forward, but I will consider.

These postings are the author's and don't necessarily represent the opinions of MathWorks.