4

I have been struggling with this problem for about a week. I have been trying to convert a script into a function for research purposes. The problem is that the code has a lot of conditional existing statements for variables, so certain variables won't exist in the workspace when they are checked for (this is why it works well as a script).

Varargin is not a solution to this problem because some of the function INPUTS won't exist.

Workspace

var1 = 1
var2 = 2
var4 = 4

Code to turn into function

if exist('var3','var')
   disp('var 3 exists')
else
   disp('var 3 does not exist')
end

The following function will NOT work because it is calling variable 3 which does not exist.

calling function

runCode(var1, var2, var3, var4)

I originally wrote this prior my function and made the code check for isnan instead of exist, but it's not great practice and since the function is called often, I don't want to have to update this function setup whenever changes to the code are made.

  if ~exist("var1", "var"), var1= NaN; end 
  if ~exist("var2", "var"), var2= NaN; end 
  if ~exist("var3", "var"), var3= NaN; end 
  if ~exist("var4", "var"), var4= NaN; end 

I don't want to use eval, and loading the workspace has caused me issues because a bunch of figures are present and it messes up the figure count in a later portion of the code. The only ideas I have right know is have a setup script for the previous if statements, or somehow save all of the workspace data into a struct or something and then assign the values the corresponding who string (gives workspace variable names).

Thanks for any ideas you guys might have

Dev-iL
  • 23,742
  • 7
  • 57
  • 99
Daniel me
  • 171
  • 1
  • 13
  • 1
    So you have a bunch of variables in your function that may exist in the workspace or not. If your code depends on the existence of these variables you won't have a choice but to check if they exist and create them if they don't. There is no way around that. I personally would wrap each occurence of a variable with a function, e.g. runCode( safevar(var1), safevar(var2), safevar(var3), safevar(var4) ) and check for the existence within safevar and create the variable via eval if it does not exist. – Mouse On Mars Aug 31 '18 at 21:32
  • 1
    I would approch this problem using the [inputParser](https://www.mathworks.com/help/matlab/ref/inputparser.html) object. You can designate name-value pairs so you could call the function, `runCode('var1', var1, 'var3', var3, ...)` and have the inputParser assign a default value to any `var#`'s not entered. Alternative to using name value pairs, the input parser lets you expand structs, so you could have a struct with fieldnames, `{'var1', 'var3'} then pass the struct into `runCode(S)`. Also, see [this answer](https://stackoverflow.com/questions/50276776/variable-arguments-in-matlab) – Khlick Sep 01 '18 at 03:38
  • “loading the workspace has caused me issues because a bunch of figures are present and it messes up the figure count in a later portion of the code.” I don’t understand this. Some code depends on whether there are figure windows open? You can’t fix that changing your code to a function, figure windows are global things, the count is identical in the base workspace and in the workspace of a function you call. – Cris Luengo Sep 01 '18 at 15:37
  • As I understand it, you have a script that does something different depending on which variables exist. You can do this in a function too, using `evalin`, but I don’t know if that is any better than keeping the script. I don’t see any other workarounds. A function should only read values passed into it, so you need to call the function with the relevant variables. It should not behave differently if a variable is defined or not. I’d say keep it as a script! – Cris Luengo Sep 01 '18 at 15:40

3 Answers3

4

The current script behaves differently depending on the existence of a set of variables with pre-defined names. This is difficult to replicate in a function, because a function should not read the calling workspace's values. This is possible to do, of course:

try
   var1 = evalin('caller','var1');
catch
   % do nothing, the variable doesn't exist in the caller, it won't exist here
end

But this really bad practice, and no different from a script. And OP specifically said to not want to use eval.

There is an alternative, which I'm hesitant to recommend because it is almost as evil as the above. We're going to define a function whose input arguments are not based on order, but on name:

runCode(var1)

will behave differently than

runCode(var2)

But the following two statements will behave identically:

runCode(var1,var2)
runCode(var2,var1)

Skeeved out? You should be!

The trick is to use inputname as follows:

function runCode(varargin)
for ii = 1:nargin
   switch inputname(ii)
      case 'var1', var1 = varargin{ii};
      case 'var2', var2 = varargin{ii};
      case 'var3', var3 = varargin{ii};
      case 'var4', var4 = varargin{ii};
      otherwise,   error('Illegal input argument')
   end
end

The rest of the function would be the body of OP's script, which contains code as follows:

if exist('var1','var')
   % ...
end

That is, first we see which variables are passed to the function, next we see which variables exist. It should be possible to rewrite the script itself to replace the exist checks with lookups into a list of input argument names.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
2

You could make the inputs be in arg pairs, so you call it like:

runCode ( 'var1', 123, 'var2', 456, 'var4', 789)

Where your function is

function runCode ( varargin )
  defaults.var1 =[];
  defaults.var2 =[];
  defaults.var3 =[];
  defaults.var4 =[];

  for ii = 1:2:nargin
    if isfield ( defaults, varargin{ii} )
      defaults.(varargin{ii}) = varargin{ii+1};
    else
      Throw error?
    end

Then you change the check on var3 to check if the value defaults.var3 is empty.

matlabgui
  • 5,642
  • 1
  • 12
  • 15
0

Thanks for all of the feedback. Since the function I want to call is sometimes called from the same place but with different variables existing, I can't vary how the parameters are set up. So, what I have done might make you guys cry but I have a setup script which I call before the function which checks if the parameters exist or not, and set them to a NaN if they don't. Its the exact same solution I had before but in a script.

PS I agree this should be left a script but they really want it to be a function ¯_(ツ)_/¯

Daniel me
  • 171
  • 1
  • 13