Afterthoughts on implicit expansion

Matlab release R2016b introduced implicit arithmetic expansion, which is a great and long-awaited natural expansion of Matlab’s arithmetic syntax (if you are still unaware of this or what it means, now would be a good time to read about it). This is a well-documented new feature. The reason for today’s post is that this new feature contains an undocumented aspect that should very well have been documented and even highlighted.

The undocumented aspect that I’m referring to is the fact that code that until R2016a produced an error, in R2016b produces a valid result:

% R2016a
>> [1:5] + [1:3]'
Error using  + 
Matrix dimensions must agree.
 
% R2016b
>> [1:5] + [1:3]'
ans =
     2     3     4     5     6
     3     4     5     6     7
     4     5     6     7     8

This incompatibility is indeed documented, but not where it matters most (read on).

I first discovered this feature by chance when trying to track down a very strange phenomenon with client code that produced different numeric results on R2015b and earlier, compared to R2016a Pre-release. After some debugging the problem was traced to a code snippet in the client’s code that looked something like this (simplified):

% Ensure compatible input data
try
    dataA + dataB;  % this will (?) error if dataA, dataB are incompatible
catch
    dataB = dataB';
end

The code snippet relied on the fact that incompatible data (row vs. col) would error when combined, as it did up to R2015b. But in R2016a Pre-release it just gave a valid numeric matrix, which caused numerically incorrect results downstream in the code. The program never crashed, so everything appeared to be in order, it just gave different numeric results. I looked at the release notes and none of the mentioned release incompatibilities appeared relevant. It took me quite some time, using side-by-side step-by-step debugging on two separate instances of Matlab (R2015b and R2016aPR) to trace the problem to this new feature.

This implicit expansion feature was removed from the official R2016a release for performance reasons. This was apparently fixed in time for R2016b’s release.

I’m totally in favor of this great new feature, don’t get me wrong. I’ve been an ardent user of bsxfun for many years and (unlike many) have even grown fond of it, but I still find the new feature to be better. I use it wherever there is no performance implication, the need to support older Matlab releases, or the possibility of incorrect results due to dimensional mismatch.

So what’s my point?

What I am concerned about is that I have not seen the new feature highlighted as a potential backward compatibility issue in the documentation or the release notes. Issues of far lesser importance are clearly marked for their backward incompatibility in the release notes, but not this important major change. A simple marking of the new feature with the warning icon () and in the “Functionality being removed or changed” section would have saved my client and me a lot of time and frustration.

MathWorks are definitely aware of the potential problems that the new feature might cause in rare use cases such as this. As Steve Eddins recently noted, there were plenty of internal discussions about this very thing. MathWorks were careful to ensure that the feature’s benefits far outweigh its risks (and I concur). But this also highlights the fact that MathWorks were fully aware that in some rare cases it might indeed break existing code. For those cases, I believe that they should have clearly marked the incompatibility implications in the release notes and elsewhere.

I have several clients who scour Matlab’s release notes before each release, trying to determine the operational risk of a Matlab upgrade. Having a program that returns different results in R2016b compared to R2016a, without being aware of this risk, is simply unacceptable to them, and leaves users with a disinclination to upgrade Matlab, to MathWorks’ detriment.

MathWorks in general are taking a very serious methodical approach to compatibility issues, and are clearly investing a lot of energy in this (a recent example). It’s too bad that sometimes this chain is broken. I find it a pity, and think that this can still be corrected in the online doc pages. If and when this is fixed, I’ll be happy to post an addendum here.

In my humble opinion from the backbenches, increasing the transparency on compatibility issues and open bugs will increase user confidence and result in greater adoption and upgrades of Matlab. Just my 2 cents…

Categories: Low risk of breaking in future versions, Stock Matlab function, Undocumented feature

Tags: ,

Bookmark and SharePrint Print

10 Responses to Afterthoughts on implicit expansion

  1. I am looking into the possibility of adding a “compatibility consideration” to the release note.

    Implicit expansion was pulled from the final release of R2016a for performance reasons. There were no other factors involved in the decision.

  2. TheBlackCat says:

    Your client is lucky the problem got caught. A lot of such issues will likely go unnoticed. For example, basically anything with a mathematical operation followed by the use of linear indexing (such as in a for loop) will seem to work fine but will give mathematically incorrect results. I know you ideally shouldn’t be doing this, and I assume MATLAB internal code doesn’t do it very much, but I see code like that all the time from people with less of a programming background, and sometimes your algorithm requires it.

    I had always assumed the reason MATLAB hadn’t implemented this feature over the last 15 years or so was that it was too big of a backwards-compatibility break.

  3. David B says:

    If nothing else I hope that the exercise of debugging this proved as a rude wake-up call for the author of that original and terrible code in the try/catch. They should be ashamed.

    • @David – while I fully agree with you that it’s not good coding style/practice, I’ve seen much worse client codes. Most Matlab users don’t have a degree in computer science, and sadly enough even CS grads often exhibit deplorable coding. In fact, my personal experience has been that only a minority of Matlab users have high-quality code. Most Matlab users use Matlab as an engineering tool and not as an end to itself: as long as something works, they don’t mind if it’s nice-looking – they just move on to solving the next problem. In this sense, the snippet above is beautiful in its simplicity, and to hell with the CS purists…

    • TheBlackCat says:

      What would your alternative be? The equivalent one I can think of would be:

      if numel(dataA)~=1 && numel(dataB)~=1 && any(size(dataA)~=size(dataB))
          dataB = dataB';
      end

      A more strict test would be:

      if numel(dataA)~=1 && numel(dataB)~=1 && ndim(dataA)==2 && ndim(dataB)==2 && any(size(dataA)~=size(dataB)) && all(fliplr(size(dataA))==size(dataB))
          dataB = dataB';
      end

      So yes, probably from a code correctness standpoint you are probably right. However, from a readability and maintainability standpoint their solution is pretty elegant. Will it be slower? Yes, but in many cases not enough to make a difference. Does it have corner cases that it doesn’t handle? Yes, although those may not be relevant or may get caught later. But even without a comment I can tell in an instant what their code is doing, while it would take me some time to figure out what either of the two examples I posted did.

      If you have another approach that is as simple and easy-to-read as the above case then of course I will retract that. But otherwise, the best algorithm from a CS standpoint isn’t necessarily the best approach once you have to start involving humans and want to be able to figure out what your code is doing 3 years down the road.

    • David B says:

      @TheBlackCat From the limited information we have available we are assuming the data is a vector. If that is the case then I think something like this code snippet would work nicely and is perfectly human readable.

      if isrow(a) && ~isrow(b) || iscolumn(a) && ~iscolumn(b)
          b = b';
      end
  4. Guillaume says:

    Well, presumably, the code is operating on vectors, so the alternative could be

       dataA(:) + dataB(:)

    But really, the proper alternative would have been to find out why the data does not come with the expected shape rather than take a gamble and flip it. I’m with David B, the code is a strong indication that something is very wrong in the algorithm somewhere and that one day, given some particular input, it’s going to break in even more unpleasant ways.

    • TheBlackCat says:

      It is hard to say without seeing more code. It may very well be that the data can only come in a few formats, so transposing it is the correct thing to do.

    • @DavidB + @Guillaume + @TheBlackCat – if I remember correctly, my client wanted the code to continue processing only when the 2 inputs were both vectors, although possibly of different dimensionality. So, [1,2,3] should be combinable with [3;4;5] but not with [3;4] (which would error out downstream). In such cases the try-catch block gives the expected results, but a(:)+b(:) would not have.

      As I said in the post, the code snippet is just a simplified version and the actual coding details don’t really matter. The important thing in my opinion is that for this specific use-case, a functional change in Matlab R2016b caused fully-legitimate code to return different results, and since the functional change was not documented as having a compatibility aspect, this caused an operational problem that was unacceptable. In this respect, it really does not matter whether the client’s code was due to bad coding or to explicit design.

Leave a Reply


Your email address will not be published. Required fields are marked *