VBA doesn't short-circuit
VBA does not support short-circuiting - apparently because it only has bitwise And/Or/Not etc operations. From the VBA language specification: "Logical operators are simple data operators that perform bitwise computations on their operands." In this light, it makes sense that VBA was designed with true = &H1111
and false = &H0000
: this way logical statements can be evaluated as bitwise operations.
The lack of short-circuiting can cause problems
Performance: the ReallyExpensiveFunction()
will always be run when this statement is evaluated, even if it is not necessary by the result of the left hand side of the condition
If IsNecessary() And ReallyExpensiveFunction() Then
'...
End If
Errors: if MyObj is Nothing, this conditional statment will result in a runtime error because VBA will still try to check the value of Property
If Not MyObj Is Nothing And MyObj.Property = 5 Then
'...
End If
The solution I've used to implement short-cirtcuiting behavior is nested If
s
If cond1 And cond2 Then
'...
End If
Becomes
If cond1 Then
If cond2 Then
'...
End If
End If
This way the If statements give the short-circuit-like behavior of not bothering to evaluate cond2
if cond1
is False
.
If there is an Else clause, this creates duplicate code blocks
If Not MyObj Is Nothing And MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO"
End If
Becomes
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO" 'Duplicate
End If
Else
MsgBox "BOO" 'Duplicate
End If
Is there a way to rewrite If
statements to preserve the short-circuit behavior, but avoid duplication of code?
Perhaps with another branching statement like Select Case
?
To add context to the question, here is the specific case I'm looking at. I'm implementing a hash table that handles collisions by chaining them in a linked list. The underlying array size is enforced to be a power of two and the hashes are distributed into the current array size by truncating them to the appropriate length.
For example, suppose the array length is 16 (binary 10000). If I have a key that hashes to 27 (binary 11011), I can store it in my 16 slot array by keeping only the bits within the limit of that array size. The index where this item would be stored is (hash value) And (length of array - 1)
which in this case is (binary 11011) And (1111)
which is 1011
which is 11. The actual hash code is stored along with the key in the slot.
When looking up an item in the hash table in a chain, both the hash and the key must be checked to determine that the correct item has been found. However, if the hash doesn't match, then there is no reason to check the key. I was hoping to gain some tiny intangible amount of performance by nesting the Ifs to get the short-circuit behavior:
While Not e Is Nothing
If keyhash = e.hash Then
If Key = e.Key Then
e.Value = Value
Exit Property
Else
Set e = e.nextEntry
End If
Else
Set e = e.nextEntry
End If
Wend
You can see the Set...
is duplicated, and thus this question.
VBA doesn't short-circuit VBA does not support short-circuiting - apparently because it only has bitwise And/Or/Not etc operations.
Data Types. The AndAlso operator is defined only for the Boolean Data Type. Visual Basic converts each operand as necessary to Boolean before evaluating the expression. If you assign the result to a numeric type, Visual Basic converts it from Boolean to that type such that False becomes 0 and True becomes -1 .
In short-circuit evaluation, Visual Basic is allowed to stop evaluating operands if it can deduce the final result without them. For example, consider the expression A AndAlso B.
In Java logical operators, if the evaluation of a logical expression exits in between before complete evaluation, then it is known as Short-circuit. A short circuit happens because the result is clear even before the complete evaluation of the expression, and the result is returned.
As a more general apprach, I suggest to introduce condition flags and make usage of assigning comparison results to booleans:
dim cond1 as boolean
dim cond2 as boolean
cond1 = false
cond2 = false
' Step 1
cond1 = MyObj Is Nothing
' Step 2: do it only if step 1 was sucessful
if cond1 then
cond2 = MyObj.Property = 5
end if
' Final result:
if cond2 then
msgbox "Yay"
else
msgbox "Boo"
end if
By "chaining" those condition flags, every step is safe, you see the final result in the last condition flag and you don't do unnecessary comparisons. And, to me, it keeps readable.
EDIT 2014-07-09
I usually never omit block delimiters and I consequently set every statement of control structures on a new line. But in this case, you can carefully get a very dense notation that reminds on short-circuit notation, also because the VBA compiler initiates the variables:
dim cond1 as boolean
dim cond2 as boolean
dim cond3 as boolean
dim cond4 as boolean
cond1 = MyObj Is Nothing
if cond1 then cond2 = MyObj.Property = 5
if cond2 then cond3 = MyObj.Property2 = constSomething
if cond3 then cond4 = not isNull(MyObj.Property77)
if cond4 then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
I could agree to this. It's a clear flow to read.
EDIT 2021-03-21
Thanks to @Tom's comment, one can write it simpler:
dim cond as boolean
cond = MyObj Is Nothing
if cond then cond = MyObj.Property = 5
if cond then cond = MyObj.Property2 = constSomething
if cond then cond = not isNull(MyObj.Property77)
if cond then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
@Tom explains the advantages in his comment below. I fully agree with this. I can only imagine some situations while debugging, when I would like to have separated results of the conditions, and therefore explicitely with four different variables.
There is a way. You're not guaranteed to like it. But this is one of those carefully constructed cases where Goto
comes in handy
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
Goto JUMPHERE
End If
Else
JUMPHERE:
MsgBox "BOO" 'Duplicate
End If
A short-circuited code to implement a short-circuited condition!
Alternately, if instead of MsgBox "BOO"
is some long and convoluted code, it can be wrapped in a function and that can be written twice with minimal impact/overhead.
Regarding the specific use case, the multiple Set
operations will have a minimal performance impact and hence, if one wants to avoid using Goto
(still the most globally efficient approach, codesize + performance wise, avoiding creation of dummy variables, etc. - won't matter, though for such a small piece of code) there is negligible downside in simply repeating the command.
Just to analyze (your sample code) how much can be gained by different methods...
In terms of performance, a jump is usually more expensive than comparison (which happens very quickly in ALU vs. the jump which could lead to a disruption in the code cache, maybe not at these sizes, but still jumps are expensive).
And normal assignment by value would be at best as fast as a pointer-assignment or sometimes worse (this is VBA, can't be 100% sure of the p-code implementation)
So, depending on your use case / expected data, you can try to minimize average number of jumps per iteration in your loop and reorder the code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With