Add listener programmatically for each object method

Hello there!
I have the following situation: Let's say I have a class:
classdef Memoizer < handle & matlab.mixin.Heterogeneous
methods
function obj = Memoizer()
... constructor code
end
function val = veryExpensiveMethod(obj, argsAsObj)
... some very time consuming code
end
function val = calledByListener(obj, methodName, argsAsObj)
... some code to check if method was called before and there are results cached
fcn_handle = str2func(methodName);
val = fcn_handle(argsAsObj);
end
end
end
Is it possible to add a listener to the specific method veryExpensiveMethod, where the listener will have access to the input argument argsAsObj of the method and is called automatically, without using notify()? The callback should be calledByListener. Essentially, the idea would be, that I can implement the method veryExpensiveMethod without thinking about firing events, but each call to the method will automatically fire such an event, and consequently call calledByListener.
I saw that you can add listeners to object properties (preGet, postGet, preSet, postSet) - which is essentially what I need, but I need it for a method call - e.g., preCalled.
In the example it is important that the class inherits from Heterogeneous. This is crucial, thus I cannot use RedefineDot operations in order to trigger events, which is not compatible with Heterogeneous. Anyways, dumping the Heterogeneous from the class definition would not solve the problem entirely, since RedefineDot is only triggered when object methods are called publicly (from the outside), but not if one method calls another method inside the object. In addition, RedefineDot does not have access to the method call's input arguments.
So, is it possible to have automatic/programmatic listener creation based on a method name, that creates a preCalled listener for a method? I can use the call stack, if that helps.
EDIT:
I forgot to add the object's reference as first input to all the methods. This, unfortunately, changes the requirements. The input argument argsAsObj is supposed to reflect the fact, that veryExpensiveMethod can be considered as a method whos entirety of input values can be passed as an object.
Best regards and thanks a lot in advance!
TE

댓글 수: 8

So what exactly would you hope this listener could do for your method?
If you want to avoid performing the expensive calculations repeatedly for the same set of inputs, consider storing a memoize object to a helper function in one of your object's properties. Have your expensive method call the memoize object. This will call the helper function if the inputs are not in the memoize object's cache or return the cached answer if it is.
Hey!
My initial thought was that I can create a <hidden> mechanic in a class file, that automatically caches results of any class method that is called. With this mechanic, any other member of my team could inherit this class (you could say a 'memoizer' class - I renamed it in the initial post) and benefit from automatic memoization without thinking about it and without taking care of that in any way.
veryExpensiveMethod would in fact be a method of a class that would inherit from Memoizer, so that the listener, and the mechnic, would be part of that child class.
For this I first tested RedefineDot but found, that there are multiple issues with this approach, e.g., it is not mixable with Heterogeneous, only public method calls are routed to RedefineDot, input arguments are not forwarded to RedefineDot. This brought me to listeners.
My initial thought was that I can create a <hidden> mechanic in a class file, that automatically caches results of any class method that is called.
No, there is no such attribute.
veryExpensiveMethod would in fact be a method of a class that would inherit from Memoizer, so that the listener, and the mechnic, would be part of that child class.
No. memoize is a function that accepts a function handle and returns an object that can be used to cache inputs to and outputs from that function handle. Here's an example of its use:
M = memoize(@expensiveFunction);
tic; M(5), toc % Call the function, takes a long time due to the pause() call
ans = 25
Elapsed time is 2.022727 seconds.
tic; M(6), toc % Call the function, takes a long time due to the pause() call
ans = 36
Elapsed time is 2.004542 seconds.
tic; M(5), toc % Use cached results -- much quicker, no pause()
ans = 25
Elapsed time is 0.003743 seconds.
If we clear the cache, M(5) goes back to calling the function (with its pause call, to simulate an expensive computation) again.
clearCache(M);
tic; M(5), toc % No result for M(5) in the cache, calls pause()
ans = 25
Elapsed time is 2.004922 seconds.
You could use this by creating M in the constructor using a handle to the "payload" function (whatever actually performs the computations that take a long time) and storing it in a property of your object. Your expensive function would then call its payload function via this property rather than calling it directly.
function y = expensiveFunction(x)
y = x.^2;
pause(2)
end
What exactly is the value in avoiding notify? Even if class methods did innately raise PreCall events, as you desire, the number of lines of code that it would add to your classdef file to listen for them would be the same as inserting calls to notify.
Thomas Ewald
Thomas Ewald 2024년 5월 2일
편집: Thomas Ewald 2024년 5월 2일
@Catalytic Hi, thanks for the reply! The actual benefit would only be that one would not be required to add the 'notify' line in their code. In the project I am working on, many people contribute, and all would have to remember and implement this line. My hope was to get some 'behind the scenes' mechanic, that nobody needs to know and remember how to use, that just works for you in the background. It would however just be a nice gimmick.
However, if such a hidden mechanic is not possible, then storing calculation values (and input arguments) in a property inside the object and asking if this value is already computed for the given inputs would also not be too difficult. With that I mean, that if people have to deal themselves with firing events, they can also deal themselves with implementing a cache property.
@Steven Lord Thanks again for the reply!
Currently, Memiozer class would be the superclass for actual classes implementing these expensive payload methods. Therefore, the Memoizer does not know a priori which methods there will be. I was thinking about using Matlab's memoize for my purposes already, but this would mean, that I have to deal with dynamic properties, which are created for each method that the child class implements. I would like to avoid that.
Instead I will try to have a central storage (e.g., a dictionary), that stores a number of Matlab's memoize classes corresponding to the method's name. A small method in Memoizer class would take care of the routing. Then, only one line of code would change inside and outside the object, depending on it you want to cache results or not.
A last question regarding this, which botheres me, is: How does Matlab's memoize deal with objects as inputs to the method. Specifically, how is equality determined, and can I rely on it identifying identical data inside objects also on nested objects? I don't need the handle to be identical (==), but the content. My guess is, that memoize calls isequal, is that correct?
From this statement on the documentation page, I believe it will call isequaln to determine if the inputs are the same as a cached set of inputs but I'm not 100% certain.
"The input arguments are numerically equal to cached inputs. When comparing input values, MATLAB treats NaNs as equal."
I recommend contacting Technical Support to ask this question (and asking that they file an enhancement request for the documentation to describe how determining when to use the cache is handled when one of the inputs is an object.)
Thanks for the comment, I will do that.

댓글을 달려면 로그인하십시오.

 채택된 답변

In the project I am working on, many people contribute, and all would have to remember and implement this line.
It wouldn't be so hard for the project leader to write a code-modifying tool that inserts the notify() commands automatically, e.g.,
S=readlines('myclass.m')
S = 16x1 string array
"classdef myclass" "" " properties" " a" " end" " " " methods " " function func1(obj,argsObj)" "" " end" " function func2(obj,argsObj)" "" " end" "" " end" "end"
for i=1:numel(S)
if startsWith(S(i),whitespacePattern+"function")
S(i)=S(i)+"; notify(stuff);";
end
end
S
S = 16x1 string array
"classdef myclass" "" " properties" " a" " end" " " " methods " " function func1(obj,argsObj); notify(stuff);" "" " end" " function func2(obj,argsObj); notify(stuff);" "" " end" "" " end" "end"

댓글 수: 3

That's a neat trick! Thanks a lot!
You're welcome, but if this is the solution you end up using, please Accept-click the answer.
I will go for a slightly different implementation (instead of listener and notify, I go for direct method call to a method all childs inherit).
However, the answer solves the problem as requested, so I mark it as accepted answer.

댓글을 달려면 로그인하십시오.

추가 답변 (0개)

카테고리

도움말 센터File Exchange에서 Performance and Memory에 대해 자세히 알아보기

질문:

2024년 4월 30일

댓글:

2024년 5월 3일

Community Treasure Hunt

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

Start Hunting!

Translated by