Loren on the Art of MATLAB

November 12th, 2009

Empty Arrays with Flow of Control and Logical Operators

After reading last week's post on calculating with empty arrays, one of my colleagues mentioned some other behaviors with empty arrays that have tripped him up in the past. Today I will discuss how empty arrays work in the contexts of flow of control expressions (both conditional and looping, i.e., if and while) and short-circuit operators (i.e., && and | |).

Contents

Empty Arrays in Flow of Control

Let me first start with plain empty arrays in flow of control situations. For example, what will this code do?

    E = [];
    if E
       disp('Empty is true')
    else
       disp('Empty is false')
    end

Readers who remember my comment on last week's blog will correctly guess that the empty expression for the if statement is treated as false. Why? The way I think about it is this. If I am looking for locations of some condition in an array, and I don't find them, I end up with an empty output. This very output is the kind of expression I am likely to want to use, somehow, in an if statement. Let's try it to be sure.

E = [];
if E
    disp('Empty is true')
else
    disp('Empty is false')
end
Empty is false

The situation gets a bit more complicated if there is a logical expression for the if or while statement that has an empty array as one of its elements. Let me show you what I mean. Paraphrasing the documentation,

 There are some conditions however under which while evaluates as true
 on an empty array. Two examples of this are
                  A = [];
                  while all(A), doSomething, end
                  while 1|A, doSomething, end

Let's see what's going on in each of these examples. In the first one, the function all is being called with an empty input. According to the second reference below (on empty arrays), the function all is one of the functions that returns a nonzero value for empty input. Let's see.

allE = all(E)
allEislogical = islogical(allE)
allE =
     1
allEislogical =
     1

The way I think about this is that there are no false values in E, hence the true result.

Empty Arrays with Logical Operators

The second expression involves an elementwise logical operator ( | ). In this case, the first part of the expression, 1, is true, so the second part, after the elementwise or, is never evaluated. So the fact that an empty result returns false never comes into play here. Why? Because & and | operators short-circuit when and only when they are in the context of if or while expressions. Otherwise, the elementwise operators do not short-circuit.

In contrast, the logical operators, && and | |, always short-circuit, regardless of context.

Short-circuit Logical Operators (| | and &&)

The next important idea to remember is that the short-circuit logical operators expect scalars as the inputs for the expressions. This means that an empty array, not being a scalar, may cause you some grief if you are unprepared for that situation. Let me show you what I mean. Compare the following 2 code snippets.

true || E
ans =
     1
try
    E || true
catch ME
    disp(ME.message)
end
Operands to the || and && operators must be convertible to logical scalar values.

In the second snippet, the expression E || true

produced an error, because E isn't a scalar value. Once the error occurs, the second operand is never evaluated. Contrast that with the snippet, where the first input evaluates to true. Short-circuiting then takes over and the second operand, which would cause an error in this context, is never evaluated.

Examples

Here are a few more code examples to help you see the patterns. Try to figure out the answers before reading the results.

if []
    disp('hello')
else
    disp('bye')
end
bye
true | []
ans =
     []
[] | true
ans =
     []
true || []
ans =
     1
try
    [] || true
catch ME
    disp(ME.message)
end
Operands to the || and && operators must be convertible to logical scalar values.
if true | []
    disp('hello')
else
    disp('bye')
end
hello
if [] | true
    disp('hello')
else
    disp('bye')
end
bye
if true || []
    disp('hello')
else
    disp('bye')
end
hello
try
    if [] || true
        disp('hello')
    else
        disp('bye')
    end
catch ME
    disp(ME.message)
end
Operands to the || and && operators must be convertible to logical scalar values.

References

Here are a bunch of references to the MATLAB documentation where all of this information is covered.

Empty Thoughts?

The behaviors with empties in MATLAB are, I believe, consistent and useful. Nonetheless, the behaviors have lots of details to master and can be confusing. If you have any thoughts on the matter, please respond here.


Get the MATLAB code

Published with MATLAB® 7.9

13 Responses to “Empty Arrays with Flow of Control and Logical Operators”

  1. Naor replied on :

    I would love to hear people’s thought on the practice of relying on short-circuit behavior instead or careful isempty checking. For example, if you had a
    if (A || B), dosomething, end;
    in your code, and B can potentially be empty, would you consider it safe to leave as is? What about readable? There could of course be more involved expressions, where you would have to look carefully to make sure an empty array would not cause an error.
    Thanks,
    -n

  2. Cris replied on :

    I understand “if []” evaluating to true. That’s good. I also see why the logical operators do what they do. But this is confusing to me:

    >> all([])
    
    ans =
    
         1
    
    >> any([])
    
    ans =
    
         0
    

    For any array that is not empty, if ALL returns true, ANY does too. The empty array seems to be the only exception!

  3. Loren replied on :

    Chris-

    I understand and sympathize with your thoughts on any/all and empty. I think of all and any looking for exceptions, in some way. So, for all, if there are no false values, as there are not with empty, then the result is true. For any, I think with the opposite polarity. If I can find any true values then it’s true but empty has not true values. It boils down to empty have neither trues nor falses, and any and all using that to decide truth.

    –Loren

  4. Mikhail replied on :

    Crosslink from StackOverflow: http://stackoverflow.com/questions/1710299/corner-cases-unexpected-and-unusual-matlab

  5. Gautam Vallabha replied on :

    Naor,

    I think you are conflating two issues: (1) the use of short-circuit operators, and (2) the use of non-logicals in conditional expressions.

    The first item seems unobjectionable. Short-circuit operators are extremely useful and can make the code more expressive.

    On the second item (non-logical scalars), I tend to agree with you. It seems better — in terms of maintainability and clarity — to say:

      if all(vec1 ~= 0) || all(vec2 ~= 0)
          doSomething;
      end
      if ~isempty(vec3)
          disp('vec3 is not empty');
      end
    

    instead of

      if vec1 || vec2
          doSomething;
      end
      if vec3
          disp('vec3 is not empty');
      end
    

    even though they are equivalent.

  6. Jiro replied on :

    Loren, thanks for your explanation in response to Cris’s question. I think it makes sense. For ALL, it’s probably more efficient to look for any occurrence of false. Once it finds at least one false, then it can return false. For ANY, it’s more efficient to look for any occurrence of true. Once it finds at least one true, then it can return true.

  7. Matt Fig replied on :

    Loren

    While I understand the outcome of all([]) is true as designated by TMW, I must say that there is an objection that can be raised from traditional formal logic considerations. If, given

    E = [];

    we can think of the statement:

    all(E)

    as being translated, “All elements of E are nonzero,” as the documentation suggests, then we have the problem of existential import.
    For example, a corresponding statement which may be more easily considered is the following,

    “All circle-squarers are non-mathematicians.”

    We can say that this statement is true only when we consider the mental concept of a circle-squarer, because our minds can imagine a circle-squarer even if it is contradictory to do so. But this is not true when we consider the tacit term ‘real’ in the statement. As in:

    “All (real) circle-squarers are non-mathematicians.”

    This statement MUST be false because, in traditional formal logical terms, the term ‘circle-squarer’ fails to designate.
    Now back to E. We immediately see that the term ‘elements of E’ fails to designate, and thus the statement, “All elements of E are nonzero,” must be false. I think this is why people have a hard time rationalizing TMW designation of all(E) as true.
    It should also be noted that in modern formal logic, such statements have no requirement for existential import. If TMW is using modern formal logic, which it probably should, then ALL statements which contain a term that does not designate are trivially true. I.e., both

    “All elements of E are non-zero.”

    and

    “All elements of E are zero.”

    are both true. Thus there is no reasoned defense of TMW designation which cannot also be made to support the opposite designation. It is an arbitrary choice that had to be made one way or the other by TMW; it is better memorized than rationalized.

    At least that is how I see it. ;-)

  8. Ben replied on :

    Matt/Loren,

    Defining all([])=true has the additional advantage that all([A;B])=(all(A)&all(B)) holds when either array is empty.

    The older versions used to generate an error for “if []” didn’t they? (although “if nan”, “if {}” and “if i” still do!) I guess that why I still prefer to write “if ~isempty(x) && all(x(:))” when it’s needed.

  9. Loren replied on :

    Ben-

    Another good way to look at the behavior! Thanks.

    –Loren

  10. Loren replied on :

    Ben

    if {} errors because the expression has to evaluate to something numeric, even if empty. Same for i. Neither one can be converted to a logical array.

    –Loren

  11. Marcel Zwiers replied on :

    It’s not exactly on topic but what I miss in the try-catch syntax is a flow as in the if-elseif-end-syntax. Thus I would like to see a try-elsetry-catch block. I think it’s a very natural thing to have, no?

    Best,
    Marcel

  12. Loren replied on :

    Marcel-

    Please feel free to use the link at the right to make this enhancement suggestion. I personally don’t mind instead using another try-catch inside the catch block. I find the nesting clarifies my thinking.

    –Loren

  13. LyVe replied on :

    This behavior has tripped me up quite a few times now.

    Although the rules make sense individually, they are inconsistent*.

    What trips me up is when I use an if statement and add a condition later. So I start with

    if A then B
    

    and then decide that C is a condition too, so I turn it into

    if A & C then B
    

    only to have things crash if A is empty - which was previously not a problem.

    I find it counterintuitive that adding code concerning C changes the way A is treated. This makes error-checking a lot more difficult, until you learn to take countermeasures.

    I appreciate being able to use empty as false: if the variable has no value, it definitely doesn’t have the right one. This is very intuitive for me. However, I’m now considering teaching myself not to use this shortcut (and adding a short-circuited ~isempty instead), because it might trip me up later, unless I can learn to remember that when adding a condition to an if or while, I also need to add a short-circuited isempty…

    I’d really like for the short-circuiting operators to evaluate in the same way that if and while do; not just for empties but also for arrays and such. Is there a particular reason that they require scalar values? Could we get ||| and &&& (for instance) to short-circuit for arrays (including empties)?

    * I must admit that it’s quite probable that there is no consistent way to handle this, other than never accepting [] where a logical is expected. This would be more robust for the user (smaller chance of a small change causing large problems), but less convenient. So maybe all of this simply can’t be helped.

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.

  • Loren: Dirga- You might consider looking at the function interp2. –loren
  • Dirga: I do not know whether the term I used is correct or not. I have set of measurement data. It is the density of...
  • Loren: Alex- You could overload sort in the same way that you create any class method. That would get you the shorter...
  • Alan Hester: can you put infomation in the interlaced images that the subconscious will pick up on and remember that...
  • Andrew Kraev: Hello, here is simple code to calculate for an optional vector a[i], i=1,..,n the sum by i of products,...
  • Alex: It would be nice to be able to sort an array of objects of a custom class, provided the class implements lt()...
  • Stu: Excellent post. No, I wasn’t aware of that property of for...end structures. Thank you!!
  • Loren: Eleanor- You either need an algorithm to determine which 0s should be replaced by NaN values or you will need...
  • Eleanor: Loren- I have a different sort of problem with NaNs and 0s. My data has zeros where data is missing, but...
  • Aslak Grinsted: Here’s another fun piece of code inspired by cellular automata. This one generates what looks...

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