Anonymous functions behavior is weird

I encountered a weird behavior with anonymous functions
take a look at this class:
classdef object1 < handle
properties
x;
end
methods
function this = foo(this, x)
this.x = x;
this.print();
end
function this = foo1(this, x)
this.x = x;
end
function this = foo2(this, x, y)
if x > y
this.x = x;
else
this.x = y;
end
end
function this = print(this)
disp(['printing ' num2str(this.x)]);
end
end
end
Now because all the functions return this, I can use it to chain commands:
object1().foo(10).foo1(20).foo2(10, 20).foo(1:10)
printing 10
printing 1 2 3 4 5 6 7 8 9 10
ans =
object1 with properties:
x: [1 2 3 4 5 6 7 8 9 10]
Ok, this is nice, now I want to send this little functionality as a callback function to some operation
so let's throw this in an anonymous function:
@() object1().foo(10).foo1(20).foo2(10, 20).foo(1:10)
ans =
function_handle with value:
@()%T0.1%1%.foo(10).foo1(20).foo2(10,20).foo(1:10)
So far, so good...
now when I try to pu it in a variable it getss weird:
func = @() object1().foo(10).foo1(20).foo2(10, 20).foo(1:10)
Error: This statement is incomplete.
and weird gets even weirder:
@() object1().foo(10).foo1(20).foo2(10, 20).foo(1:10);
func = ans
func =
function_handle with value:
@()%T0.1%1%.foo(10).foo1(20).foo2(10,20).foo(1:10)
func();
printing 10
printing 1 2 3 4 5 6 7 8 9 10
Expected one output from a curly brace or dot indexing expression, but there were 0 results.
Error in @()%T0.1%1%.foo(10).foo1(20).foo2(10,20).foo(1:10)
This is strange as I saw already that this line of code returns a value
This behavior persists if i send it as an argument to a function instead of saving in a varialbe
that much at least is expected...
I know I can work around that by using function notation instead of dot indexing
but I like to avoid the pyramid of doom when I can, as this code is by far less readable:
func = @() foo(foo2(foo1(foo(object1(), 10), 20), 10, 20), 1:10)
func =
function_handle with value:
@()foo(foo2(foo1(foo(object1(),10),20),10,20),1:10)
>> func()
printing 10
printing 1 2 3 4 5 6 7 8 9 10
ans =
object1 with properties:
x: [1 2 3 4 5 6 7 8 9 10]
Sugestions?

4 Kommentare

TADA
TADA am 22 Nov. 2018
Bearbeitet: TADA am 22 Nov. 2018
I also know it can be worked around using named functions instead of anonymous functions but, it is still a weird behavior
besides, anonymous functions are extremely useful in writing callback handles. Its fast and its clear and does not polute your workspace with billions of functions which are never reused. And they can be easily employed in command window when handling callbacks.
Joseph Transom
Joseph Transom am 7 Aug. 2019
Bearbeitet: Joseph Transom am 7 Aug. 2019
Chaining-fail is understandable because ML doesn't do it. What's pretty annoying is that ML's anonymous function stuff has stupid issues in scripts.
Something simple: ML's timetable functionality is poor, so you have to do a bunch of things by varfun(). (alternatively, you have to save the VariableNames and RowTimes, table2array the timetable, do your stuff, then re-timetable it.
Consider the following, with rData a timetable (78 × 18).
To divide every column by 100 you have to do
f_100 = @(x) x/100;
rData = varfun(f_100, rData); % works every time the script is run
means = varfun(@mean, rData); % works every time, too - small mercies
However let's say you want the std(rData)*sqrt(12) - you're annualising a monthly std().
So. Since this is a column operation on a timetable, you know in advance that ML can't do it, so you know you have to varfun - easy enough.
f_sd = @(x) std(x)*sqrt(12); % Not rocket surgery: what could go wrong?
sds = varfun(@f_sd, rData); % works - ONCE.
If you run that script again (say, after adding some code to collate the outputs), you get this ->
"f_sd" was previously used as a variable, conflicting with its use here as the name of a function or command.
f_sd appears nowhere except the two lines above.
There is a clear all; at the top of the script.
So ML is ballsing it up somewhere.
Lest readers imagine that I haven't wasted even more time trying to get ML to do what it ought to, I also tried
sds = varfun(@std, rData).*sqrt(12); % and just *12
No joy:
Undefined operator '.*' for input arguments of type 'timetable'.
Of course it didn't work - anyone who would expect otherwise clearly deserves some sniffy supercilious dismissal of their dismay.
In R or Python (pandas) or any SQL scripting language, performing column-wise calculations on a time- or date-indexed dataframe is a trivial undertaking.
Likewise, in those other platforms, functions behave as they ought to, and RStudio and [GUI of choice for Python] don't eventually cling to 4GB of RAM if left open for 24 hours with an environment that contains a 100-line script and a single 7MB data object.
Maybe I'm just expecting too much of modern subscription software; conversely, maybe ML is Kodak in an age of ubiquitous digital cameras.
I haven't been this disappointed in a piece of 'brand' software since I was forced to use MapInfo in preference to QGIS.
Fortunately (for me) I'm just tidying up some ML code before porting it to R. And work's paying for the license.
Rik
Rik am 7 Aug. 2019
My personal experience is that the newer data structures need several years to mature. If there happens to be a function that does what you need, it works like a charm. Otherwise you're often better off converting to another data type (which is generally the point I choose not to convert back to the newer data type).
Some (most?) of the things you describe sound like bugs. Have you tried reaching out to support? On the few occasions I sent them feedback they were quick with a response.
Also, I have never had your experience with such extreme ram usage, except maybe using Chrome. I'm curious what kind of script/function would cause that.
Your rant would carry more weight if you'd used valid examples.
f_sd = @(x) std(x)*sqrt(12);
sds = varfun(@f_sd, rData); % works - ONCE.
doesn't even work once, since the correct syntax for passing anonymous functions (which are already function handles) is:
sds = varfun(f_sd, rData); % NO @
And I don't get any issue making it work more than once:
>> rData = timetable(datetime + hours(1:100)', rand(100, 1), randi(50, 100, 1));
>> f_sd = @(x) std(x)*sqrt(12)
f_sd =
function_handle with value:
@(x)std(x)*sqrt(12)
>> sds = varfun(f_sd, rData)
sds =
1×2 timetable
Time Fun_Var1 Fun_Var2
____________________ _________________ ________________
08-Aug-2019 00:11:10 0.926696527765659 52.4154037040119
>> sds = varfun(f_sd, rData)
sds =
1×2 timetable
Time Fun_Var1 Fun_Var2
____________________ _________________ ________________
08-Aug-2019 00:11:10 0.926696527765659 52.4154037040119
As for,
sds = varfun(@std, rData).*sqrt(12);
Well, yes varfun returns a timetable unless you specify 'OutputFormat', 'Uniform', and multiplying a timetable by anything is undefined. What would you expect the result of the multiplication of 12 by a timetable containing a numeric variable, a text variable and a variable containing a objects?

Melden Sie sich an, um zu kommentieren.

 Akzeptierte Antwort

Guillaume
Guillaume am 22 Nov. 2018

0 Stimmen

I don't think the problem stems from anonymous function per se. I believe that the root of what you're seeing is that the chaining you're using in
object1().foo(10).foo1(20).foo2(10, 20).foo(1:10)
is not really allowed in matlab. You're indexing (dot is an indexing operation) the return value of a function which is illegal.
I know that mathworks have put in place some machinery to support some common use cases of dot indexing with functions, but clearly when you combine that with anonymous function (which also have some special machinery since they are closures) it breaks down.
See Philip's answer to a related question where he states that you shouldn't use chaining and also this answer to another question. Philip, if he's still around, would probably be the best person to explain the nitty gritty of why it brearks down with anonymous functions, but the bottom line is that you shouldn't be using function chaining even if it works in some cases. There's no guarantee it'll keep on working in future versions of matlab.

8 Kommentare

TADA
TADA am 22 Nov. 2018
I see...
so is this not allowed due to functions allowing multiple return values?
func=@() object1().foo(10).foo1(20).foo2(10, 20).foo(1:10);
Doesn't return an error for me (tested on R2017b as well), but then trying to call that anonymous function does.
So my conclusion would be the same as Guillaume: here be dragons.
so is this not allowed due to functions allowing multiple return values?
That's not the reason. The dot operator is an indexing operator just like () and {}. They all result in a call to subsref. You're not allowed to subsref the result of a function. For example you're not allowed to do:
sin(1:10)(5) %() indexing asking for the 5th element of the array returned by sin
but under some limited circumstances matlab tolerates
myfunc().something %dot indexing of the value returned by myfunc
Perhars, matlab should be more strict and never allow it since the cases where it works are not documented.
To avoid storing intermediate results I once used the line below
%doesn't work for v=7.10 or v=10.1
v=str2double(subsref(version,struct('type','()','subs',{{1:3}})));
So I understand I should not have done this? Of course this might introduce errors on unexpected output by the inner function, but isn't that error handling the responsability of the author of the end result? Just like it would be with intermediate saved results?
Explicitly calling subsref on the result of a function is fine and perfectly allowed. What you can't do is let matlab guess that you meant to subsref that result by using the () operator.
subsref(version,struct('type','()','subs',{{1:3}})) %no problem. Parser is fine with that
version(1:3) %obviously parser doesn't know you want to subsref instead of passing arguments
version()(1:3) %not allowed, parser doesn't understand it needs to subsref the return value
For TADA using subsref wouldn't help. He/she would be chaining subsref calls that would be even less readable than the pyramid of doom created by chaining functional notation.
TADA
TADA am 22 Nov. 2018
I tried to fool the parser by overriding subsref but it changed nothing.
Thank you for clarifying this issue.
TADA
TADA am 25 Nov. 2018
Bearbeitet: madhan ravi am 25 Nov. 2018
@Guillaume, your references to Philips answers sent me thinking and experimenting
So when using a property in the end of the chain it gets even more ridiculous
func = @() object1().foo(10).foo1(20).foo2(10, 20).foo(1:10).x
this line worked in a script but not in command window...
guess its a lost cause...
Guillaume
Guillaume am 25 Nov. 2018
What's frustrating is that we know that there are some undocumented cases where dot indexing a temporary is supported by matlab, as stated by Philip. Unfortunately, for a programmer, undocumented is the same as non-supported.
So, you should never use dot indexing on a temporary (function return value).

Melden Sie sich an, um zu kommentieren.

Weitere Antworten (0)

Produkte

Version

R2017b

Gefragt:

am 22 Nov. 2018

Kommentiert:

am 7 Aug. 2019

Community Treasure Hunt

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

Start Hunting!

Translated by