The first set of the triad is && , || and ! — the "logical and, logical or, logical not." This group is best used within an expression, to combine a bunch of conditions together.
The second is and , or and not— the "logical composition operators". These operators are intended to be used between statements to conditionally combine them.
The Same on the Surface But Different Underneath
Both set of operators calculate expressions the same way.That is: both && and and take two operands and logically and them together; both || and or take two operands and logically or them together. Likewise, ! and not both negate the expression.
apple = true
orange = false
apple && orange # => false
apple and orange # => false
apple || orange # => true
apple or orange # => true
!apple # => false
not apple # => false
!orange # => true
not orange # => true
This is where the similarities end. There are two fundamental differences.
Difference #1: Short-Circuiting
&& and || can short-circuit the remainder of the expression; whereas and and or only short-circuit between the two operands.# Only if the souschef fails to wash, will the chef wash.
# but in either case, the chef always slices the apple.
souschef.wash(apple) or chef.wash(apple) and chef.slice(apple)
# if the souschef does peel the orange, nothing more happens.
# else, the chef peels the orange (and if successful) slices it.
souschef.peel(orange) || chef.peel(orange) && chef.slice(orange)
Difference #2: Precedence
and, or and not are among the lowest precedence operators in the Ruby language. In contrast, &&, || and ! are relatively higher. This is particularly important when your expression includes other operators that sit between these two sets in the precedence ranking.Here's the complete set of operators in Ruby, in order of precedence.
Symbol |
Name
|
---|---|
::
| Constant resolution operator |
[] []=
| Element reference operator and element set operator |
**
| Exponent operator (raise to the power of...) |
! ~ + -
| Unary operators: not, complement, unary plus, unary minus |
* / %
| Multiplicative operators: multiply, divide, modulo |
+ -
| Addition and subtraction operators |
<< >>
| Bitwise shift operators (left and right) |
&
| Bitwise "and" operator |
| ^
| Bitwise "or" and bitwise "xor" operators |
> < >= <=
| Comparison operators |
== === <=> =~
| Equals, same object, "spaceship"/compare, and regex match operators |
&&
| Logical "and" operator |
||
| Logical "or" operator |
.. ...
| Range operators: inclusive and exclusive |
? :
| Ternary operator |
=
| Assignment operator |
defined?
| Symbol existence operator |
not
| Logical negation operator |
or and
| Logical composition operators |
if unless while until
| Flow control operators |
begin/end
| Block expression |
Table 1 — Ruby operators in order of precedence. Highlighted sit between the two triads.
For example:
Using the Logical "and" operator, this statement is non-sensical...
# The chef washes (nil), and food becomes
# whether she is successful AND whether orange is truthy
food = orange && chef.wash(food)
For example:
Using the Logical "and" operator, this statement is non-sensical...
# The chef washes (nil), and food becomes
# whether she is successful AND whether orange is truthy
food = orange && chef.wash(food)
whereas, with the logical composition "and", we have a series of statements strung together:
# The food is orange
# And the chef washes the food (i.e. the orange)
food = orange and chef.wash(food)
That's a lot of detail to keep in mind. The point is to appreciate that even though the two operator sets appear to behave the same superficially, deep down, they are different and not always interchangeable.
Conclusion
Where you go from here depends on the skill level of the "average committing developer" for your product, both present and future (see Dryfus Model of Skill Acquisition for definitions).Novice and Advanced Beginner
In this case, you're likely working in a shop that leans more toward being productive over engineering a solution.
The most professional move here is to keep it as simple as possible. In this way, the system has a better chance of being maintainable.
Avoid and prohibit use of the logical composition operators (and, or, and not) all together. There is no significant loss of expressiveness (i.e. there are no significant statements that you suddenly can't write) and it avoids this confusion completely.
Competent
There is room/expectation for some deliberation, but you're mostly writing application code and not so many frameworks and/or libraries. That is: your code is rather concrete and therefore more straightforward.
In these circumstances, you can certainly make use of the additional expressiveness that the logical composition operators bring. However, you'll likely want to define the context for when programmers ought to use each. Include include these in your coding style guide.
Here's a pair as an example...
Guideline #1: Use &&, || and ! for boolean expressions
Focus/limit your use of the symbolic set of logical operators (&&, || and !) for composing boolean expressions.
For example:
if params[:order] && params[:order][:coupon_code]
not
if params[:order] and params[:order][:coupon_code]
... and ...
return false if !Rails.env.production? ||
!Spree::Config[:check_for_spree_alerts]instead of
return false if !Rails.env.production? or
!Spree::Config[:check_for_spree_alerts]
if params[:order] && params[:order][:coupon_code]
not
if params[:order] and params[:order][:coupon_code]
... and ...
return false if !Rails.env.production? ||
!Spree::Config[:check_for_spree_alerts]instead of
return false if !Rails.env.production? or
!Spree::Config[:check_for_spree_alerts]
Guideline #2: Use and, or, and not for flow control
Use the word set (and, or and not) for conditionally stringing statements together. In other words, for flow control.
Examples:
respond_with(@order) { render :edit } and return
not
respond_with(@order) { render :edit } && return
... and ...
get_from_cache(id) or load_from_disk(id) and send_email()
instead of
respond_with(@order) { render :edit } and return
not
respond_with(@order) { render :edit } && return
get_from_cache(id) or load_from_disk(id) and send_email()
instead of
(get_from_cache(id) || load_from_disk(id)) && send_email()
Proficient and Expert
Chances are, you're not reading this article. :) But for completeness' sake, the advice here is to use the full power of the toolset you have.
Avoid unnecessary parenthesis and loss of expressiveness by using the right operator for the job.
Example: Pre-Checks
(from https://github.com/spree/spree/blob/master/core/lib/spree/responder.rb):
def to_html
super and return if !(on_success || on_failure)
has_errors?
? controller.instance_exec(&on_failure)
: controller.instance_exec(&on_success)
end
Here, there's an early return at the top of the method if there are no hooks registered. Pre-checks like this are reasonably easy to learn idioms and help avoid verbose guards (e.g. an if statement surrounding the contents of the method).
Notice, BDQ's thoughtful use of both kinds of logic operators that visually convey the difference uses (i.e. he used and specifically for logical composition and || for boolean math).
Example: Precedence
(from https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/has_many_association.rb)def count_records
...
@target ||= [] and loaded! if count == 0
...
end
def count_records
...
(@target ||= []) && loaded!() if count == 0
...
end
(I added the second set of parens after "loaded!" to call out the fact that they would be needed had the method required parameters.)
References
- Avdi Grimm covered the heart of this years ago: http://devblog.avdi.org/2010/08/02/using-and-and-or-in-ruby/
- Ruby Operators (with precedence): http://phrogz.net/programmingruby/language.html
- Dryfus Model of Skill Acquisition: http://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition
No comments:
Post a Comment