Search code examples
matlabrecursionworkspace

matlab - access variable in specific workspace


I need something similar to evalin, but original evalin of Matlab cannot be used recursively. For example, I have function f0 which calls 2 other functions f11 and f12:

function f0()
    [v1, v2] = deal(1, 1);

    f11();
    f12();

    disp(v1);
end

Functions f11 and f12 use variable v1, and both call function f2:

function f11()
    v1 = evalin('caller', 'v1');

    f2();

    assignin('caller', 'v1', v1);
end

function f12()
    v1 = evalin('caller', 'v1');

    f2();

    assignin('caller', 'v1', v1);
end

And the function f2 should use both variables v1 and v2 of f0's workspace:

function f2()
    v1 = evalin('caller', 'v1');    % get variable v1 from f11 or f12
                                    % since there is already a variable v1 
                                    % in f11 and f12's workspaces
    % TODO: get v2 from f0

    if v2 == 1
        v1 = v1 + 1;
    end

    assignin('caller', 'v1', v1);

end

Is there anyway to make the TODO possible without using v2 = evalin('caller', 'v2') in f11 and f12 ?


Solution

  • This is possibly one of the worst way of organising your data transfer between functions. Workspaces of functions are separated for a good reason, to keep things clean and organised.

    You are trying to bypass all safeguards put in place by people who designed this language (and many other language who share this separation of workspace/scope). These "bypass" functions are available in Matlab for punctual usage when you are prototyping. They are not intended for heavy usage or for final solution (most of them won't be compilable actually).

    It is

    • potentially ultra confusing (for you later on, but even worse if someone else has to use your code).
    • very hard to debug (you almost have to know the value of a variable in advance to know if the import from another workspace was right).

    You say in the comment that passing variable as parameter is complicated because of their number, but every time you call something like x = evalin('caller', 'x'); you have to write 1 full line of code to retrieve your value anyway. And then assignin('caller', 'x', x); is another line of code to send it back ... that's madness.

    Would it not be simpler (and shorter) to just have x in your function input parameters ?? (it is still faster to have 100 variables in input than to have 200 full lines of codes to retrieve then resend these values from uncertain locations).


    Recommended (if practical): pass variable in parameter

    To make variable passing easy you could for example gather them all in a structure (or a cell array):

    [v1, v2] = deal(1, 1);
    myVars.v1 = v1 ;
    myVars.v2 = v2 ;
    
    myVars = f11(myVars); %// you only have one variable to pass into your functions
    myVars = f12(myVars); %// just make sure you retrieve it in output too
    
    disp(myVars.v1);
    

    And then

    function myVars = f11()
        myVars = f2(myVars);
    end
    

    And so on, as long as you pass the variable to the next function, then retrieve it in output, this would work with any level of recursion.


    Recommended (if above not possible): Use nested functions

    If you define your function f11() inside your f0(), the variable which have the same name in the 2 functions will be shared (visible by at both levels). Read the documentation for more detail. This way you would not need your numerous call to evalin/assignin because the variable is know everywhere. Your function would have to be written in this form:

    function f0()
    [v1, v2] = deal(1, 1);
    
    f11();
    f12();
    
    disp(v1);
    
        function f11()
            f2();
            function f2()
                if v2 == 1
                    v1 = v1 + 1;
                end
            end
        end %// END function F11
    
        function f12()
            f2();
            function f2()
                if v2 == 1
                    v1 = v1 + 1;
                end
            end
        end %// END function F12
    
    end %// END function F0
    

    Stack Exchange does not render that, but in the Matlab editor you could notice that the "shared" variable will be highlighted in a different colour (to alert you that they have a different scope than standard variable).

    If I run f0() I do get:

    >> f0
         3
    

    Which was the expected result. The only downside, as you can see, is that if you are calling f2() from within f11() and f12(), the function f2() has to be written within each of them (so a bit of copy/paste). Unless at this level of recursion you do not have too many variable to pass so you could consider writing f2() on the side and using the standard variable passing scheme.


    Can work, but not recommended

    Now if you are still keen on writing numerous lines of codes just to pass single variables around, there are still 2 options:

    I do not like to use global so I won't detail here. Just remember that for a variable to be really global it has to be declared as global in every function where it is used.

    For the appdata method, you need a "container" which will be accessible to all your functions. You can use the "root" object for that (identifier: 0).

    So for example, when you want to store a variable you could use:

    setappdata( 0 , 'v2' , v2 ) %// store the value of `v2` in a field named `'v2'` in the root object.
    

    then in any of your function, get the value, work on it then store it back:

    function f2()
        v1 = getappdata(0,'v1')     %// get the value of v1
        v2 = getappdata(0,'v2')     %// get the value of v2
        if v2 == 1
            v1 = v1 + 1;            %// modify the value of v1
        end
        v1 = setappdata(0,'v1',v1)  %// store the value of v1
    end
    

    Apply the same principle in your base function and f11(), f12(), etc ... Just remember to always store any value back after you modified it so it is available for the next function which will need it.