16

I know that this is not what anonymous functions are made for, but just as a puzzle I tried to make a recursive function via anonymous functions. The prototype of recursive functions obviously is the factorial function. The problem is that it is difficult to make a case distinction within the anonymous functions. What I managed to do so far is following:

f=@(cn,n,f)eval('if n>1; f(cn*n,n-1,f);else;ans=cn;end');
f=@(n)f(1,n,f);

Or alternatively:

f=@(cn,n,f)eval('if n>1; f(cn*n,n-1,f);else;disp(cn);end');
f=@(n)f(1,n,f);

What is not very satisfactory is that you still cannot use this function when directly assigning, a=f(3) still produces an error, since eval does not get a value.

So my question is, can you actually do a recursive function via anonymous functions that e.g. calculates factorial in a way that allows e.g. a=f(3) with relying only on native matlab functions (or functions you can create in the command line, as I did in my example)?

PS: I know this does not have any practical use, it is just a challenge on how much you can bend and abuse Matlab's syntax.

dpmcmlxxvi
  • 1,292
  • 1
  • 9
  • 13
flawr
  • 10,814
  • 3
  • 41
  • 71
  • 1
    Do you mean anonymous function? Function handle seems to be a broader class of objects to me. But I'm not an expert, so I'm really asking. – Andras Deak -- Слава Україні Aug 26 '15 at 21:59
  • @AndrasDeak Of course I do, thank you for pointing this out! – flawr Aug 26 '15 at 22:01
  • No Matlab access at the moment, but have you tried to to just do something like this `'if n>1; f(cn*n,n-1,f); else; cn; end`? – patrik Aug 26 '15 at 22:04
  • @patrik *I* did and it didn't help:) The problem is that if you just call `f(3)` it will execute `if...` and all's well. But if you want to assign `a=f(3)`, it wants to do something like `a= if...`. We badly need a conditional ternary operator in matlab;) – Andras Deak -- Слава Україні Aug 26 '15 at 22:05
  • 7
    [This](http://blogs.mathworks.com/loren/2013/01/24/introduction-to-functional-programming-with-anonymous-functions-part-2/) might be helpful. – beaker Aug 26 '15 at 22:09
  • I think I just found what I was looking for, the `if_` function as presented [here](http://lambda.jstolarek.com/category/programming/matlab/), thank you guys anyway for helping, I have been looking for this for so long! I just need to figure out how to use this in my factorial example=) – flawr Aug 26 '15 at 22:11
  • Make sure to post an answer if you arrive at a satisfactory solution:) – Andras Deak -- Слава Україні Aug 26 '15 at 22:22
  • @beaker: That's not only helpful, it contains a finished solution including explanations. – Daniel Aug 26 '15 at 22:31
  • @AndrasDeak I did now! – flawr Sep 02 '15 at 22:37
  • Also see this [newer question](https://stackoverflow.com/questions/36310676/is-it-possible-to-have-a-recursive-anonymous-function-in-matlab) by @Sanchises. – flawr Feb 08 '18 at 10:58

1 Answers1

11

We found two possibilites now, both rely on the use of cell arrays. Note that this might not work in Octave.

The key was an implementation of a case distinction. The first one that I found, can be found here.

This method makes use of matlabs boolean values, true can be evaluated as 1 while false can be evaluated as 0.

if_ = @( pred_, cond_ ) cond_{ 2 - pred_ }();

Here we have to provide a condition as first argument, and a 2 element cell array as second argument. Each cell element should be a function handle that is called if the condition is true/not true. Our factorial function would look like this:

fac = @(n,f)if_(n>1,{@()n*f(n-1,f),@()1})
factorial_=@(n)fac(n,fac);
factorial_(10)

As @AndrasDeak commented below: The important part here is that we have a cell array of functions and not of values. This provides the short circuiting, as n*f(n-1,f) is not evaluated unless we call the corresponding function @()n*f(n-1,f).

The second method was found by @beaker and is somewhat more flexible:

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

This makes use of the fact that you can use varargin (variable amount of arguments) even in anonymous functions. When you call this function you have to alternate conditions and what should be executed if the condition is true. This one even allows a switch construct, or a if ... else if ... else if ... (...) else ... construct. When called, it will look for the first condition that is true ( find([varargin{1:2:end}], 1, 'first') ) and call the corresponding function. Our example of the factorial function looks like this:

fac = @(n,f)iif(n>1,@()n * f(n-1,f),true,@()1);
factorial_=@(n)fac(n,fac);
factorial_(10)

EDIT: Fun fact: What we are doing with the line

 factorial_=@(n)fac(n,fac);

is also known as applying the Y-combinator. In fact we can write that as

 Y = @(f)@(x)f(x,f);
 factorial_=Y(f);
flawr
  • 10,814
  • 3
  • 41
  • 71
  • 1
    I suggest emphasizing in both cases that `@()` is needed to protect the argument from evaluation. Since when you call, for instance, `mean(rand(3))`, matlab first evaluates `rand(3)`, then passes that matrix to the `mean` function. If you would omit the `@()` part, the `iif` would try to evaluate the then-meaningless function. Main point: the `iif` function appears to short-circuit at the first `true` argument, but it doesn't. – Andras Deak -- Слава Україні Sep 03 '15 at 10:07