views:

151

answers:

2

I've written a very simple GUI in MATLAB that will convert temperatures. It is meant to serve as a tutorial for a class of students. A strange thing has happened though. As with any MVC design pattern, there is a model object, a view object and a controller function. In order to set the output field of the GUI (the converted temperature), you can run this line in the controller function itself:

set(views.outputTextField,'string',num2str(round(model.outTemp)));

where views.outputTextField is a GUI text field to display the converted temperature and model.outTemp is the converted temperature. Pretty straightforward. The views object has references to all the GUI uicontrols and this updates the field with the newly converted temperature in the model object.

However, I would rather have view functions in the view object, so I attempted to create a line like this:

views.updateOutputField = @()set(views.outputTextField,'string',...
    num2str(round(model.outTemp)));

Same line as before, just that now it is an anonymous function in the view object. This way I could call the function from the controllers as simply views.updateOutputField(); and keep the view logic out of the controller logic. But this method won't work! (It will work with the get() function.)

Instead I have to do the following:

views.updateOutputField = @updateOutputField

function updateOutputField()
    set(views.outputTextField,'string',num2str(round(model.outTemp)));
end

By separating out the function (redundantly) instead of just using an anonymous function, it works again. What!? This makes no sense to me. The view and model objects are global and the anonymous function works with get(). Does anyone have a clue what's going on here?

+3  A: 

Both approaches are not equivalent. Values in the body of anonymous function (aka lambda) are being frozen, see example below:

>> ii = 2;
>> val = @() ii+2;
>> val()
ans =
     4
>> ii=5;
>> val()
ans =
     4

You can do following to make it work:

views.updateOutputField = @(outTemp) ...

If you want to know how MATLAB captures the workspace context, use function FUNCTIONS on anonymous function.

Your example is a little bit more complicated because your view and model exist in the nested workspace but the essence is the same.

As side note: kudos for teaching also an important design pattern (MVC) in Matlab class!

Mikhail
This is absolutely true. Ironically, after working within the MVC design paradigm for a while and illustrating it to some students, I realized that it is better not to use anonymous functions anyway! For organizational reasons, it's better that they be assigned a name (in anything other than the simplest conditions) Nonetheless, thanks for this very clear explanation.
@ruzel: please, mark an answer as "accepted"
Mikhail
Sorry it took me so long to do that Mikhail; still getting used to how StackOverflow works!
+2  A: 

Mikhail has the right answer. I'll elaborate a bit...

From the MATLAB documentation for anonymous functions:

Anonymous functions commonly include two types of variables:

  • Variables specified in the argument list. These often vary with each function call.

  • Variables specified in the body of the expression. MATLAB captures these variables and holds them constant throughout the lifetime of the function handle.

When you make a call to SET inside your anonymous function, you access fields of your two structure variables views and model. These values are held fixed at what they were when the anonymous function was created. That doesn't matter for the graphics handles stored in views, since these never change (unless you are deleting and recreating graphics objects). This is why calling GET in your anonymous function works fine, since it only uses the unchanged graphics handles in views. However, the values in model change, so you would want to pass them in to the anonymous function as an argument. For example:

views.updateOutputField = @(model) set(views.outputTextField,'String',...
                                       num2str(round(model.outTemp)));

When you instead create your updateOutputField function, you are creating a nested function. Nested functions have access to the variables that exist in the outer function within which they are nested, which is why you don't have to pass views and model as arguments. When these variables change in the outer function, that change is visible in the nested function as well (unlike anonymous functions).

gnovice
OK. It took me 5 months to get around to thinking about this again, but now that I understand, this is brilliant! I can greatly improve the MVC design now. As a matter of fact, I am deleting and recreating graphics objects in the UI and this will allow me to do so be recreating the graphics objects with the variables from the model. Much improved! Thanks so much.