MATLAB Answers

Is it possible to write several statements into an anonymous function?

215 views (last 30 days)
Patrick Mboma
Patrick Mboma on 8 Oct 2012
Edited: Walter Roberson on 25 Jul 2017
Hi all,
I would like to write the following statements into a function
z=zeros(5);
z(1,1)=x(1)*cos(x(2));
z(3,4)=log(x(3));
Is is possible to write this into an anonymous function somehow?
thanks,
Patrick
  9 Comments

Sign in to comment.

Answers (10)

Walter Roberson
Walter Roberson on 8 Oct 2012
Edited: Walter Roberson on 8 Oct 2012
Also, an if/else can be coded in function form by using
FHEXEC = @(FH) FH();
FHSELECT = @(TF,CONDITION) TF(CONDITION==[true,false]);
IF = @(CONDITION,TRUEFUNC,FALSEFUNC) FHEXEC( FHSELECT([TRUEFUNC,FALSEFUNC],CONDITION) )
Then, for example,
if x>3; sin(x); else cos(x); end;
can be coded as
IF(x>3, @(sin(x)), @(cos(x)))

Jamie
Jamie on 16 Apr 2016
Anonymous functions support only expressions, so multiple-statement procedural code must be transformed into functional style. The pain points in this transformation are control statements, such as if/then, which are not available, and assignment, which is unnatural at best in functional languages. (Matlab-without-statements could be considered a functional language, albeit an awful one.)
One way to think of the procedural-to-functional transformation is to imagine each statement (potentially with side-effects) as a function applied to the environment that returns a new environment.
To take your example, I might use a structure 's' to represent a local environment, and each statement transforms the environment. Stealing from Walter's clever helper functions:
ASGN = @(A,B,S) subsasgn(A, struct('type', '()', 'subs', {B}), S);
then:
x = [ 1 2 3 ];
f1 = @(s) setfield(s, 'z', zeros(5)); % z = zeros(5);
f2 = @(s) setfield(s, 'z', ASGN(s.z, {1,1}, x(1)*cos(x(2)))); % z(1,1)=x(1)*cos(x(2));
f3 = @(s) setfield(s, 'z', ASGN(s.z, {3,4}, log(x(3)))); % z(3,4)=log(x(3));
f4 = @(s) s.z; % equivalent to "return z"
f4(f3(f2(f1(struct()))))
To invoke functions that have no output value as statements, we need a "no-op" function that passes the environment unmodified but forces evaluation of other parameters:
nop = @(s, varargin) s; % evaluates other arguments while passing environment unmodified
x = [ 1 2 3 ];
f1 = @(s) setfield(s, 'z', zeros(5)); % z = zeros(5);
f2 = @(s) setfield(s, 'z', ASGN(s.z, {1,1}, x(1)*cos(x(2)))); % z(1,1)=x(1)*cos(x(2));
f3 = @(s) setfield(s, 'z', ASGN(s.z, {3,4}, log(x(3)))); % z(3,4)=log(x(3));
f4 = @(s) nop(s, fprintf('hello world\n')); % fprintf('hello world\n');
f5 = @(s) s.z; % equivalent to "return z"
f5(f4(f3(f2(f1(struct())))))
To perform if/then, again I'll borrow Walter's idea: if we're operating on continuations we can implement a ternary operator to choose the appropriate continuation, and then evaluate it. If I have
ftrue = @(s) setfield(s, 'z', ASGN(s.z, {2,2}, 99));
ffalse = @(s) setfield(s, 'z', ASGN(s.z, {3,3}, 10));
and a simple ternary (without short-circuit)
ternary = @(c,varargin) varargin{2-logical(c)}; % returns second or third argument
then I can implement a conditional which applies one or the other continuations
ftrueorfalse = @(s) feval(ternary(s.z(1,1) < 0, ftrue, ffalse), s);
Finally to avoid assigning temporary variables just to be able to "f5(f4(f3(f2(f1(struct())))))", we can implement the equivalent of a begin/end construct that takes any number of @(s)... style functions and composes them and returns single @(s)... function. This will also be necessary for multiple statements in an if/else block. For simplicity these helper functions are not anonymous:
% given statements, generate function that applies all of them to an environment
function f = compose(varargin)
f = @(s) applyeach(s, varargin{:});
end
% given environment and statements, apply each statement to the environment in turn
function s = applyeach(s, varargin)
for i=1:length(varargin)
s = varargin{i}(s);
end
end
Finally, all together, procedural code can be more-or-less one-to-one translated into a (admittedly ugly) composition of functions, for example:
x = [ 1 2 3 ];
z = zeros(5);
z(1,1) = x(1)*cos(x(2));
z(3,4) = log(x(3));
fprintf('hello world\n');
if z(1,1) > 0
fprintf('true branch\n');
z(2,2) = 99;
else
fprintf('false branch\n')
z(3,3) = 10;
end
% return z
Translates into:
x = [ 1 2 3 ];
f = @() feval(compose(...
@(s) setfield(s, 'z', zeros(5)), ...
@(s) setfield(s, 'z', ASGN(s.z, {1,1}, x(1)*cos(x(2)))), ...
@(s) setfield(s, 'z', ASGN(s.z, {3,4}, log(x(3)))), ...
@(s) nop(s, fprintf('hello world\n')), ...
@(s) feval(ternary(s.z(1,1) > 0, ... % "if"
compose( ... % "begin block"
@(s) nop(s, fprintf('true branch\n')), ...
@(s) setfield(s, 'z', ASGN(s.z, {2,2}, 99)) ...
) ... % "end block"
, ... % "else"
compose( ... % "begin block"
@(s) nop(s, fprintf('false branch\n')), ...
@(s) setfield(s, 'z', ASGN(s.z, {3,3}, 10)) ...
) ... % "end block"
), s), ... % "end if"
@(s) s.z ... % "return z"
), struct());
Now, as for whether this is desirable? It depends. Clearly the syntax is ugly, so you wouldn't want to do this more than you had to. Speed is an open question but might not matter depending on the application.
To me it seems the snide objections to the original question are mostly ignorant of why you would want to use anonymous functions at all, much less why multi-statement anonymous is somehow never needed while single-expression anonymous is useful. Yes, much of what anonymous functions can do is also possible with inner (nested) functions, but this also argues that there is no point to anonymous functions at all, since you can always just define a one-line inner function and refer to it.
I can see at least a couple reasons for anonymous functions instead of inner functions that go beyond single statements:
  1. Anonymous functions bind variables from the outer scope at the time they are defined, unlike inner functions that use variables from the outer scope at the time they are used. Variables from the outer scope that are used in inner functions can be dangerous in that the scope of effect is more difficult to trace, akin to global variables. Anonymous functions do not suffer from this.
  2. Some algorithms are much more intuitive when written procedural style with multiple assignments.Patrick's original question is an example of this, and while equivalent behavior can be achieved with reshape, it is less clear what the output will be.
Regards,
-Jamie
  4 Comments
Jamie
Jamie on 2 May 2016
Hmm, it appears that set() seems to have its own rules, which are version-dependent. 2014a blows up on your feval(xyxy) but 2015b succeeds, and x = set(gcf, 'visible','on'); also succeeds in 2015b. But even in 2015b, if I define my own 'noret' function (not anonymous)
function noret(a, b) % no return value
fprintf('a, b: %d %d\n', a, b);
end
then feval of an anonymous function that calls noret still fails on too many output arguments.
The best I could do is to make my own (non-anonymous) feval that does not attempt to get an output value and just returns a dummy value so it will not barf when used in an expression.
function dummy = feval_discard(f, varargin)
feval(f, varargin{:}); % nargout will be zero
dummy = 0;
end
I'm stumped as to how to do this 'anonymously'.

Sign in to comment.


Matt J
Matt J on 8 Oct 2012
No, anonymous functions have to consist of a single statement, but your commands can be expressed as a single statement this way
z=@(x) reshape( [x(1)*cos(x(2)), zeros(1,16), log(x(3)), zeros(1,7)], [5,5]);
Not sure why you wouldn't just put it in a normal function or nested function, though.
  6 Comments
per isakson
per isakson on 8 Oct 2012
Do you assume that "reparsing each specification and writing a separate file in each case" is a problem because of the time it takes?
"I can do it without knowing in advance how many specifications". When and how is data for the new specifications supplied?

Sign in to comment.


Walter Roberson
Walter Roberson on 8 Oct 2012
z = @(x) subsasgn(subsasgn(zeros(5), struct('type', '()', 'subs', {1,1}), x(1)*cos(x(2)))), struct('type', '()', 'subs', {3,4}), log(x(3));
(Note: bracket count might be wrong)
You might want to use an auxillary function:
ASGN = @(A,B,S) subsasgn(A, struct('type', '()', 'subs', B), S);
z = @(z) ASGN(ASGN(zeros(5), {1,1}, x(1)*cos(x(2)))), {3,4}, log(x(3)));

Patrick Mboma
Patrick Mboma on 25 Jul 2017
Edited: Walter Roberson on 25 Jul 2017
I asked this question 5 years ago and it is still active today. I had almost forgotten that I did ask such a question, which led me to ask variants of the same questions. My sense is that this is an important problem.
The statements composing the function can have if/else statements, there could be intermediary terms but no for or while loops.
The quickest and dirtiest thing to do is obvious. One can simply write all the statements as a string and evaluate them. But most people would argue against using eval. Eval has drawbacks for sure but clarity need not be one of them. It is always possible to evaluate a string inside a dedicated function to avoid messing up a particular workspace.
I very much appreciate all the proposals that have been suggested. One of the main messages I get from them is that what would be otherwise a simple function should be turned into a difficult-to-read set of statements if one tries to create an anonymous function.
My guess is that functions like these should be straightforward to handle in other languages. In any case, for me, this is so far the most challenging programming task I have faced in Matlab and it seems it was already an issue long before I asked: https://www.mathworks.com/matlabcentral/newsreader/view_thread/143778

Daniel Shub
Daniel Shub on 8 Oct 2012
It is a little odd but ...
f = @(x)(reshape([x(1)*cos(x(2)), zeros(1, 16), log(x(3)), zeros(1, 7)], [5, 5]));

Andrew Cramer
Andrew Cramer on 27 Mar 2013
Edited: Walter Roberson on 27 Mar 2013
A little old but I have a solution that may be interesting to some.
Solution:
I wrote a function ExecMany(varargin) which executes each of the function handles supplied to it.
Why did I want to?
I wanted this functionality when making a GUI, by using anonymous functions I could hard code the guicontrol handles into the anonymous function which gets made when specifying callbacks.
  • A nested function could do it but when the main function finishes the variables are no longer guaranteed to exist, what happens when a nested function is called outside its parent function?
  • A function at the end of the file or in the working directory could do it but it wouldn't know what handles to use
  • Global variables could fix that, but that's messy, inelegant and the user of my GUI gets to see all my variables and play with them.
  • Reshape, or making an array etc doesn't work for functions like set(...) who don't return anything.
  • Anonymous functions can have the handles hard coded, don't pollute the global namespace and don't make gratuitous numbers of files of 1 line functions. In addition, the definition of the function is close to the uicontrol, in some cases, it can be defined along with the uicontrol.

Patrick Mboma
Patrick Mboma on 8 Oct 2012
I have a solution to my own question but I don't know whether it is the best strategy: Create an anonymous function that is more general than matlab's anonymous function.

Muthu Annamalai
Muthu Annamalai on 8 Oct 2012
Great answers everyone; the best solution for this problem might still be 'reshape' based answer by Walter, Daniel and Matt - each separately.
I would think easiest way to achieve multiple statement anonymous functions should be through evaluating them, i.e. using evalc().
Its not a great idea, but its something that works.
  2 Comments

Sign in to comment.


Are
Are on 15 Oct 2015
Edited: Are on 15 Oct 2015
This case is old, but I stumbled upon it today. The easiest way to get multiple statements into an anonymous function, is by using the eval function (or evalin). This is not fast nor space saving, but it could be useful in cases where your MATLAB program is (MATLAB compiler) compiled into an executable, and you can input anonymous functions by text.
@(x)eval('disp(1);disp(2)');

Community Treasure Hunt

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

Start Hunting!