Loren on the Art of MATLAB

July 26th, 2006

Trying to Match Behavior of a New Function to an Existing One

We might want to a write a function that mimics the behavior of another related function, specifically with respect to input shapes, etc. Ideally, we'd like to write it so we don't need to update our function if the related function changes how it works, again with respect to input shapes and sizes, for example.

Contents

Error Checking

In a perfect world, we'd do enough error checking so users get a reasonable error message when appropriate. If the error conditions change for the original function we're mimicking, we would normally need to change our function as well to match the new behavior.

Model our Function after the Arithmetic Operators

Suppose we want a function to behave like the arithmetic operators in MATLAB. Here's the relevant rule for plus, and it applies to all the elementwise operators and most of the elementwise functions in MATLAB:

  • + Addition or unary plus. A+B adds A and B. A and B must have the same size, unless one is a scalar. A scalar can be added to a matrix of any size.

The Function

We'd like to compute

Often, when I am creating a new M-file, I often include the error checking at the top of the file. In the function I describe here, I have made an effort to generalize the error checking so it could be reused, not my usual starting point!

Here's the code.

dbtype expMuSigChk
1     function y = expMuSigChk(x,mu,sigma)
2     % Exponential function with parameters mu and sigma.
3     
4     % Check sizes.
5     if chkSz(x,mu,sigma) == 0
6         error('LSBlog:sizeMismatch','Non-scalar arguments must follow MATLAB dimension matching rules.');
7     end
8     % Since chkSz passed, we can now do our computations.
9     y = exp(-0.5 * ((x - mu)./sigma).^2) ./ (sqrt(2*pi) .* sigma);
10    
11    function tf = chkSz(varargin)
12    % Collect info on nonscalarsizes and return false if they aren't all the
13    % same.
14    nonscalarsizes = {};
15    % convert sizes to strings so we can use unique on them
16    for k = 1:length(varargin)
17        if isscalar(varargin{k})
18            continue
19        end
20        nonscalarsizes{end+1} = int2str(size(varargin{k})); 
21    end
22    if isempty(nonscalarsizes)
23        tf = true;
24    else
25        if length(unique(nonscalarsizes)) > 1
26            tf = false;
27        else
28            tf = true;
29        end
30    end
31    

Notice that there are about 20 lines (including comments) for the error checking AND if MATLAB rules were to ever change, this check function here would have to change as well. It would be far too easy to forget about this and then be surprised when the behavior of expMuSigChk was no longer like MATLAB.

Alternate Program

Instead we can write a program that takes advantage of MATLAB effectively doing the error checking for us. We try to do the calculation, and if it fails, we issue and error.

dbtype expMuSig
1     function y = expMuSig(x,mu,sigma)
2     %Exponential function with parameters mu and sigma.
3     try 
4         y = exp(-0.5 * ((x - mu)./sigma).^2) ./ (sqrt(2*pi) .* sigma);
5     catch
6         error('LSBlog:sizeMismatch','Non-scalar arguments must follow MATLAB dimension matching rules.');
7     end

Comments

I plan to write on the topic of try, error checking, and using lasterror in the future, for those of you who are wondering about possibly polluting the error state of MATLAB.

What are your thoughts on taking advantage of MATLAB in the way I describe here?


Published with MATLAB® 7.2

11 Responses to “Trying to Match Behavior of a New Function to an Existing One”

  1. aslak grinsted replied on :

    Is there any standard or recommendations on how to specify the first argument to error. (Ie. ‘LSBlog:sizeMismatch’). i usually only specify one argument to error.

  2. Aravind Seshadri replied on :

    I was just curious. When

    EDU>> x = 10;
    EDU>> mu = 5;
    EDU>> sigma = [];
    EDU>> expMuSig(x, mu, sigma)

    ans =

    []

    The function returns []. I went back and checked it. When x is scalar, sigma = [] and if I do

    EDU>> x/sigma
    ??? Error using ==> mrdivide
    Matrix dimensions must agree.

    but if perform an array division

    EDU>> x./sigma

    ans =

    []

    Could you explain what is happening.

  3. Loren replied on :

    Aslak-

    I use a message id when I use error because we use the id to help us show messages in Japanese for the Windows version of MATLAB for Japan.

    I always try to use a message id when I add a warning since that is how users can selectively turn warnings on and off.

    –Loren

  4. Loren replied on :

    Aravind-

    What you are seeing is the difference between / and ./. Try x./sigma and you will see the output is also empty. For the slash operator, which is matrix division, the dimensions have to agree but for the point-wise ones, where we allow scalar expansion, we have the output the size of the nonscalar input. Empty is considered, with size 0×0, to not be a scalar (which has size 1×1) and therefore its dimensions propagate through.

    –Loren

  5. Oliver A. Chapman, PE replied on :

    As so frequently happens, this week’s column was a good exposure to new (for me) styles of programming.

    First, regarding the primary lesson from the column: Yes, the try / catch method seems like a robust method to deal with the issue of error handling. We all should be in favor of techniques that make code easy to maintain. Thanks for illustrating the technique. Plus, it takes fewer modules of code and fewer lines of code. All these are good.

    Now for the unexpected detail: In your first (complicated) batch of code you have:

    16    for k = 1:length(varargin)
    17        if isscalar(varargin{k})
    18            continue
    19        end
    20        nonscalarsizes{end+1} = int2str(size(varargin{k}));
    21    end
    

    At first, I couldn’t follow what was happening with the “if” statement and the “continue”. I thought the “continue” would take you out of the “if” statement.

    Although I’ve criticized a few sections of MatLab documentation in the past, the “continue” section served very well in this case. The documentation was clear that the continue statement terminated this cycle of the “for” loop.

    So, I was confused, I read the documentation and now I understand. Success!

    But, I don’t think this coding technique is sound because many others who will read the code will also make the same assumption that I did. Isn’t it clearer to do a negative test? Something like this:

    16    for k = 1:length(varargin)
    17        if ~isscalar(varargin{k})
    18            nonscalarsizes{end+1} = int2str(size(varargin{k}));
    19        end
    20
    21    end
    

    You even save a line of code.

  6. Paul replied on :

    Hello, I have made the experience that it can help a lot if people get further information about the error to be able to fix it. The simplest method would be to add lasterr one line above the error statement so the original error is printed and the user gets a better idea what might be wrong. Else the information is lost after the error in the catch block is thrown.
    One question, how sure can I be about error checking in Matlab functions?

  7. Loren replied on :

    Oliver-

    I don’t find the code I wrote unclear, but I definitely agree with you that yours is even clearer and that others could be confused about continue. I’m glad you learned something more about it by reading the doc!

    –Loren

  8. Loren replied on :

    Paul-

    I am not sure I understand your question about error checking in MATLAB functions, but I think you are asking if we are always careful about the state of lasterror. I can’t answer definitively, but my guess is that we’re mostly but not perfectly good about it. It is something we are aware of and have on a (long) list of things to watch for.

    –Loren

  9. Peter Meilstrup replied on :

    Goodness, that’s ugly. What your code segment does there is to take useful information that was already in the error message and hide it behind your own message (containing something about argument size rules with is pure presumption on your part — the input might be bad for any number of other reasons, making your error message a lie.)

    The code segment is idempotent to actually not doing any error checking at all, except it provides even less information to the programmer when it fails. Given the choice between no error checking at all, and that unnecessary catch block the replaces a stack trace with a likely incorrect error message, I prefer no checking.

    The purpose of exceptions is to provide useful information to the programmer via an error message and stack trace, and to give code an opportunity to clean up from failures. If you’re not willing to further either purpose, don’t catch exceptions.

  10. Loren replied on :

    Peter-

    This post was written before MATLAB had exceptions. I agree that now it would be better written using exceptions and adding a cause.

    –Loren

  11. Peter Meilstrup replied on :

    I suppose you mean adding two more functionless lines to make it something like this:

    1     function y = expMuSig(x,mu,sigma)
    2     %Exponential function with parameters mu and sigma.
    3     try
    4         y = exp(-0.5 * ((x - mu)./sigma).^2) ./ (sqrt(2*pi) .* sigma);
    5     catch theRealError
    6        theErrorWhichIsALie = MException('LSBlog:sizeMismatch','Non-scalar arguments must follow MATLAB dimension matching rules.');
    7        theErrorWhichIsALie = addCause(theErrorWhichIsALie, theRealError);
    8        throw(theErrorWhichIsALie);
    9   end
    

    Again, what information does “using exceptions and adding a cause” add to the error report that was not already present (i.e. that the program failed while calling expMuSig)?

    It is writing “error handling” code in a cargo-cult manner because you *think* you should be writing error handling code everywhere. But the code is carefully constructed to be idempotent to not writing any error handling code at all, other than it additionally gives back some false information that you have to dig down to the “cause” to reveal. What you’ve managed to prove conclusively is that this is a case where you should actually not write “error handling” code, if that code does not actually handle errors.

Leave a Reply

Wrap code fragments inside <pre> tags, like this:

<pre class="code">
a = magic(3);
sum(a)
</pre>

If you have a "<" character in your code, either follow it with a space or replace it with "&lt;" (including the semicolon).


Loren Shure works on design of the MATLAB language at The MathWorks. She writes here about once a week on MATLAB programming and related topics.

  • Jun: I totally can not believe it, Loren. You are really helpful. Thank you so much, MATLAB master!
  • Loren: Wow folks- Always lots of interest when there’s a quickie to try out! I will only make 2 general...
  • Loren: Jun- ismember is your friend here: >> [aa,ind] = ismember(Array2,Arra y1) aa = 1 1 1 1 1 1 1 ind = 1 2 1 4 4 3...
  • Dan: I like the first way better than the second way. Combining the arrays into one and running any is nice, although...
  • James Myatt: How about I = (a == 0 | b == 0); a(I) = []; b(I) = [];
  • Tunc: Hello Loren, love your blog because of such inspiring and challenging comments to such ’small’...
  • Pekka Kumpulainen: Here is my tradeoff. I usually want to keep the original variables as they are most probably...
  • Iain: Followup: Of course, to allow NaNs (counting them as non-zero): mask = (a~=0) & (b~=0); The mask says “a...
  • Matt Fig: I would usually go with something like this: y = a&b; x = a(y); y = b(y); But I was surprised to find...
  • kk: c=all([a;b]) a(c) a(b)

These postings are the author's and don't necessarily represent the opinions of The MathWorks.