2

Is there a way to declare a variable local in a nested function?

By declare I mean name a variable as usual but enforce that its scope starts in-place. Imagine creating a new nested function in the middle of a large program. There are natural variable names you wish to use and you would not want to worry whether you have to check existing variable names every single time you create a new variable.

To describe the desired effect, I'll use two examples. One minimal. One shows the problem a little better visually.

Short example

function fn1
    var = 1
    function fn2
        local var = 'a';
        function fn3
        end
    end
end

Within fn2 and fn3, var refers to the new variable with starting value 'a' while outside fn2, var with starting value 1 is still available as usual.

Long example

function fn1
    var = 1;
    var2 = 2;
    function fn2
        var2 = 'I can access var2 from fn1. Happy.'
        local var = 'a';  % remove local to run this snippet

        fn3;
        function fn3
            var2 = 'I can access var2 from fn1. Happy.'
            var = 'fn2 cannot safely use variable name var because it may have been used in fn1. But var is the natural name to use in fn2. Sad.';

            var = 1;
            var2 = 2;
        end
    end
    function fn4
        var2 = 'I can also access var2 from fn1. Also happy.'
        var = 'If only local scoping works, I would still be able to access var. Would be happy.';
    end

    fn4;
    fn2;
    var,
    var2,
end
%% desired output, but not real
>> fn1;
var =
    1
var2 =
    2

Is there a way to accomplish the above?

Currently, I do my best ensure that name variables that are not local in nature with special non-generic names and name variables that are obviously local in nature temp# where # is an integer. (I suppose clearing variables after known last use can help sometimes. But I'd rather not have to do that. Local variables are too numerous.) That works for small programs. But with larger programs, I find it hard to avoid inadvertently re-writing a variable that has already been named at a higher scoping level. It also adds a level of complexity in the thought process, which is not exactly good for efficiency, because when creating a program, not all variables are either obviously local or obviously not local. A flexible scoping mechanism would be very helpful.

I write my programs in Sublime Text so I am not familiar with the Matlab editor. Does the editor have visual guards/warning prompts against errors arising from inflexible scoping? Warning that requires visually scanning through the whole program is barely useful but at least it would be something.

Community
  • 1
  • 1
Argyll
  • 8,591
  • 4
  • 25
  • 46
  • Could you not stick the function in a different file [how to call a function of a matlab file in another matlab file](https://www.mathworks.com/matlabcentral/answers/224373-how-to-call-a-function-of-a-matlab-file-in-another-matlab-file)? – kkuilla Feb 03 '20 at 09:23
  • No. The point of nesting is passing data. Calling a function from another file does not do that. – Argyll Feb 03 '20 at 09:27
  • So data is not passed to a function via parameters of the function? Is that what you are saying? Look at the section `Separate m-files` [here](https://stackoverflow.com/a/31662210). `x1` and `x2` are data as far as I understand. Same link suggests classes as another option. – kkuilla Feb 03 '20 at 09:39
  • No, because modification may be required. Or persistent variables may be required. Data is not necessarily passed to a single function but instead may be needed (and possibly modified) by multiple functions. Yes, OOP is a natural direction. But that's OOP. I'd prefer solutions within functional style for the following reasons 1) myself and perhaps many others are not familiar with OOP; 2) I am further not familiar with OOP in Matlab (given the large number of quirks with functional Matlab already, I am not optimistic); 3) there is no easy way to convert existing programs from functional to OOP – Argyll Feb 03 '20 at 09:44
  • 1
    Can you use a [local function](https://www.mathworks.com/help/matlab/matlab_prog/local-functions.html) instead of a nested function? That way variables are not shared with the main function – Luis Mendo Feb 03 '20 at 10:02
  • @LuisMendo: No. Being able to share some variables but not others is exactly the hope. I'll edit the example. – Argyll Feb 03 '20 at 10:04
  • Instead of having variables shared by default and specifying some variables as local (which may not be possible), you could do the opposite: use local functions, so they don't share variables by default, and specify which variables you want to share using [`global`](https://www.mathworks.com/help/matlab/ref/global.html) – Luis Mendo Feb 03 '20 at 10:08
  • @LuisMendo: `global` makes variable persistent in the whole Matlab instance. That can create even more undesirable effects than nesting. The ability to nest is a major upgrade from `global` when it comes to passing data with scoping rules. – Argyll Feb 03 '20 at 10:13
  • @LuisMendo: I edited the question. I think (?) the new example shows what I mean by nesting being an upgrade. – Argyll Feb 03 '20 at 10:23
  • 3
    I think you're tying yourself in knots trying to avoid OOP. Try it, it's not that bad. – nekomatic Feb 03 '20 at 10:26
  • 1
    @nekomatic: Had I known how bad this can become, I would have. Now there is no way to back for existing programs. The problem with scoping does not stop at what I've described either. It compounds with other Matlab quirks. Think about `for` loop. There is no reason whatsoever for the iterating variable to be non-local. Except it isn't. What's worse is that the `for element=arr` syntax cannot consistently work, which forces you to iterate with array index. Now you are absolutely forced to name all iterating variables different in nested functions. Oh my beloved Matlab. – Argyll Feb 03 '20 at 10:31
  • @nekomatic: Plus, functional is only nice when modification is allowed. The moment modification is allowed. Flexible scoping is needed. Otherwise, functional is at a distinct disadvantage to OOP in what they can do. Instead, it should be an equal choice up to user preference and familiarity. – Argyll Feb 03 '20 at 10:38
  • 1
    This sounds like an [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), you have a problem with specific requirements, which can probably be met by using OOP. You don't want to adopt a different structure because it will mean a lot of work, meanwhile you want to put a lot of work in to achieve a specific variable scoping structure which doesn't sound consistent with functional programming. I advise you step back and define what you want to achieve, MATLAB is a full featured language, people will have solved similar problems without loads of workarounds! – Wolfie Feb 03 '20 at 11:27
  • @Wolfie: I did define what I want to achieve. If you mean what my program needs to accomplish, I am trying not to dump my task onto others. Instead, I want to describe a problem that is generic with a minimal example. Requiring modification is generic because multiple functions needing to access the same data is a generic situation. I tried to show the 'because' part with an example in the question. I also tried to explain how that points to a need for flexible scoping. I am looking for either a way to flexibly scope variables in nested functions or a definite no answer. – Argyll Feb 03 '20 at 11:39
  • @Wolfie: I think you see an XY problem because in your mind OOP is the default whenever flexible scoping is involved and therefore only see a question asking for an alternative to OOP. I don't start with OOP though. I am simply trying to program with in most natural way in Matlab. If flexible scoping is possible within functional, that means I can always start future programs with a functional structure, which can be the more natural/simpler/computationally efficient approach at least initially. If not, it means as long as a program *can* grow complex, OOP may be required. It's not really XY. – Argyll Feb 03 '20 at 11:47

3 Answers3

2

No, there is no way in MATLAB to declare a nested function variable to be local to that function if the variable also exists in the external scope (i.e., the function containing the nested function).

The behavior of Nested Functions is fully described in MATLAB documentation and what you are asking is not possible or at least not documented.

It is specifically stated that the supported behavior is

This means that both a nested function and a function that contains it can modify the same variable without passing that variable as an argument.

and no remedy to prevent this behavior is mentioned in the documentation.

Kavka
  • 4,191
  • 16
  • 33
2

Nested functions have a very specific use case. They are not intended to avoid having to pass data into a function as input and output arguments, which is to me what you are attempting. Your example can be written using local functions:


function fn1
    var = 1;
    var2 = 2;
    [var,var2] = fn4(var,var2);
    var2 = fn2(var2);
    var,
    var2,
end

function var2 = fn2(var2)
    var2 = 'I can access var2 from fn1. Happy.'
    var = 'a';  % remove local to run this snippet

    [var,var3] = fn3(var,var2);
end

function [var,var2] = fn3(var,var2)
    var2 = 'I can access var2 from fn1. Happy.'
    var = 'fn2 cannot safely use variable name var because it may have been used in fn1. But var is the natural name to use in fn2. Sad.';

    var = 1;
    var2 = 2;
end

function [var,var2] = fn4(var,var2)
    var2 = 'I can also access var2 from fn1. Also happy.'
    var = 'If only local scoping works, I would still be able to access var. Would be happy.';
end

The advantage is that the size of fn1 is much reduced, it fits within one screen and can much more easily be read and debugged. It is obvious which functions modify which variables. And you can name variables whatever you want because no variable scope extends outside any function.

As far as I know, nested functions can only be gainfully used to capture scope in a lambda (function handle in MATLAB speak), you can write a function that creates a lambda (a handle to a nested function) that captures a local variable, and then return that lambda to your caller for use. That is a powerful feature, though useful only situationally. Outside that, I have not found a good use of nested functions. It’s just something you should try to avoid IMO.


Here's an example of the lambda with captured data (not actually tested, it's just to give an idea; also it's a rather silly application, since MATLAB has better ways of interpolating, just bear with me). Create2DInterpolator takes scattered x, y and z sample values. It uses meshgrid and griddata to generate a regular 2D grid representing those samples. It then returns a handle to a function that interpolates in that 2D grid to find the z value for a given x and y. This handle can be used outside the Create2DInterpolator function, and contains the 2D grid representation that we created. Basically, interpolator is an instance of a functor class that contains data. You could implement the same thing by writing a custom class, but that would require a lot more code, a lot more effort, and an additional M-file. More information can be had in the documentation.

interpolator = Create2DInterpolator(x,y,z);
newZ = interpolator(newX,newY);

function interpolator = Create2DInterpolator(x,y,z)
   [xData,yData] = meshgrid(min(x):max(x),min(y):max(y));
   zData = griddata(x,y,z,xData,yData);
   interpolator = @InterolatorFunc;

   function z = InterolatorFunc(x,y)
      z = interp2(xData,yData,zData,x,y);
   end
end
Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • What do you mean by capture a local variable? As in define a handle that stores all current values of variables in scope? Or references to them? I understand that by changing the way the functions are called, local functions will work even when multiple local functions need to essentially be able to modify the same input. However, I am still averse to using local functions in every instance for two reasons. – Argyll Feb 06 '20 at 21:00
  • One is that it would make the input lines of many functions as long as the function body themselves in my current program. It won't be just where the local functions are defined either. It will be every time a local function is called. – Argyll Feb 06 '20 at 21:00
  • The other is that I am not confident with Matlab being able able to only modify the piece of data in question. Say with [Java ring buffer](https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.2/org/apache/commons/collections/buffer/CircularFifoBuffer.html) or Matlab containers.Map objects. Nesting ensures new data is added to the buffer as intended. But using input/output of local function may not do that. – Argyll Feb 06 '20 at 21:01
  • @Argyll: I've edit an example for the concept of lambda capture into the answer. Regarding long argument lists: you could put your arguments into a cell array or a struct to make these calls shorter. I think the gain in legibility is worth the verbosity. – Cris Luengo Feb 06 '20 at 21:39
  • Regarding MATLAB's ability to properly handle data: this has never been an issue, MATLAB is very stable. `containers.Map` is a handle object, which is always passed by reference (rather than by copy as is the case non-handle objects and plain old arrays). If the called function modifies the map object, the object in the caller workspace is modified, not a local copy. There never is a local copy. You need to make sure you know which objects are handle objects, and which are not. I don't like handle objects because of this... – Cris Luengo Feb 06 '20 at 21:40
  • Actually, knowing that helps me quite a bit. Is there a way to quickly check whether an object belongs to a subclass of `handle`? – Argyll Feb 07 '20 at 00:00
  • 1
    @Argyll: You can use `isa(obj,'handle')`. See [docs](https://www.mathworks.com/help/matlab/matlab_oop/handle-objects.html#btm3mqa). – Cris Luengo Feb 07 '20 at 01:39
2

Variables that are defined as input arguments are local to the function. So you can define var as an input argument of fn2*:

function fn2 (var)
...
end

However if you like to define fn2 without changing its signature you need to define an extra level of nesting:

function fn1
    var = 1;
    var2 = 2;
    function fn2

      fn2_impl([]);
      function fn2_impl (var)
        var2 = 'I can access var2 from fn1. Happy.'
        var = 'a';  % remove local to run this snippet

        fn3;
        function fn3
            var2 = 'I can access var2 from fn1. Happy.'
            var = 'fn2 cannot safely use variable name var because it may have been used in fn1. But var is the natural name to use in fn2. Sad.';

            var = 1;
            var2 = 2;
        end
      end
    end
    function fn4
        var2 = 'I can also access var2 from fn1. Also happy.'
        var = 'If only local scoping works, I would still be able to access var. Would be happy.';
    end

    fn4;
    fn2;
    var,
    var2,
end

Here fn2_impl is the actual implementation of fn2 and it inherits all variables that inherited by fn2. var is local to fn2_impl because it is an input argument.

However, I recommend local functions as suggested by @CrisLuengo. If variables need to be shared, using OO style of programming is more readable and maintainable than implicitly sharing by nested functions.

  • Thanks to @CrisLuengo that noted me that it is possible to skip inputs arguments when calling MATLAB functions.
rahnema1
  • 15,264
  • 3
  • 15
  • 27
  • Interesting workaround! If the nested function doesn’t read the value of `var`, the calling function doesn’t really need to pass anything there, no? – Cris Luengo Feb 03 '20 at 15:33
  • Thanks, In MATLAB we are forced to pass something like `[]`. In Octave I think it is possible to ignore arguments. – rahnema1 Feb 03 '20 at 15:36
  • Imagine inheriting a project written entirely with this structure :( Nice workaround though – Wolfie Feb 03 '20 at 15:47
  • It is just a recommendation. However it answers the question of declaring local variables in nested function. – rahnema1 Feb 03 '20 at 16:11
  • 1
    Out of stubbornness, I booted up MATLAB and tried this. Just changing `function fn2` in to `function fn2(var)` in OP's code turns `var` into a local variable. It is perfectly fine to still call it as `fn2`, `var` is unassigned at the start of the function, but the function then does `var = 'a'`, so that's fine. – Cris Luengo Feb 03 '20 at 16:12
  • Thanks, I didn't know that. It was based on some old documentations. But if `fn2` has a more complicated signature such as `[a,b] = fn2 (c)`. The extra level of nesting can be useful in such cases. – rahnema1 Feb 03 '20 at 16:17
  • I think you can just add whatever variables you want to make local to the end of the parameter list: `[a,b] = fn2(c,local1,local2,local3)`. It wouldn't change the way you call the function. – Cris Luengo Feb 03 '20 at 16:20
  • Thanks, I will reflect that in the answer. – rahnema1 Feb 03 '20 at 16:23
  • 1
    Nice work around! And indeed, the last inputs are optional even though there is no way to skip an input. – Argyll Feb 06 '20 at 20:23
  • @Argyll Note that if you use [varargin](https://www.mathworks.com/help/matlab/ref/varargin.html) it should be the last input and other named inputs won't be optional. – rahnema1 Feb 06 '20 at 20:51
  • @rahnema1: Good point. So in case of varargin, a second nested function is unavoidable; without varargin, simply adding the variable to function input will make it local. Does that sound right? – Argyll Feb 06 '20 at 20:54
  • OK. Except when you for some reason don't want to change the signature of the outer function you can use the nested function. – rahnema1 Feb 06 '20 at 20:56