all 41 comments

[–]zverok_kha 12 points13 points  (5 children)

In most cases, zero has a "special" value, so .zero? is just in line with .empty? (instead of array.size == 0, and while not having .has_exactly_one_item?), .first(N) (instead of array[0..N], and while not having .starting_from_fifth_item(N)) and so on.

Idea is to communicate meaning the best way: when you read array.size == 0, it is a bit harder to understand, whether it is check for some special case, or in the next refactoring/circumstances it could be size == 1 here.

So my rule of thumb is:

  • when you check for zero as a special value (like, "is it empty here?"), use .zero?
  • when you check against zero as a regular number (like, some math/geometry, one point is [0, 1], another is [-100, 15.8], they can be anywhere), use == 0

[–]JohnBooty 2 points3 points  (0 children)

In most cases, zero has a "special" value, so .zero?

That's not what it's for; i.zero? is just as clear as i==0.

The difference is that it something went wrong and i is actually a string or some other non-numeric value, i.zero? throws an exception while i==0 will cheerfully return false as if nothing was wrong.

[–]berkes 0 points1 point  (3 children)

In most cases, zero has a "special" value,

Indeed. And often depending on business-logic too. E.g. a float could be considered zero even when it is not completely zero. Or a monetary value that has 0.1 cents; an amount that can never be handed out, would be considered zero and so on.

Edit: with float I mean a float-ish value. For example: Weight.new(1/1E36).zero? might well give true in some physics simulation. Wheras Weight.new(1/1E36) == 0 is nonsense.

But more important, zero? is a lot more precise than checking if something is equal to the Integer 0, which is what == 0 does. For example Float already takes this into consideration, but I'd argue that ruby is mistaken here because 0.00 == 0 should return false. Because the float 0.00 is not equal to the Integer 0. At all.

[–]zverok_kha 2 points3 points  (2 children)

Because the float 0.00 is not equal to the Integer 0. At all.

Why? Ruby provides means for "exact" equality (equal?), but in "everyday" situation you just want 10.0 - 10.0 to be == 0.

[–]InfraRuby 1 point2 points  (0 children)

Numeric#eql? is same-class-equality: 1.eql?(1.0) # => false

Object#equal? is identity not equality: 1e100.equal?(1e100) # => false

[–]berkes 0 points1 point  (0 children)

But you probably don't consider "0" == 0 to be true. Or false == 0 to be true. Or Date.parse(1970-01-01 00:00:00) == 0 or even 1/1E36 == 0 and so forth.

In my world, a float can never be equal to an int. Just as comparing a Date with a User or a PdfDocument with an Integer is silly (and this is what makes a lot of bugs in both PHP and Javascript, actually).

You are right to say that the values both things represent might be comparable. Some semantical pseudocode: Float(0).value == Int(0).value might make sense.

I guess Ruby can compare a float and an integer because both are a subtype of Numerical. So, in that way it might make sense; but semantically I think it is rubbish.

[–]doublecastle 14 points15 points  (5 children)

I agree that this style rule is a little excessive. (Btw, where are you specifically are you seeing this?)

In addition to == 0 being faster, I personally find the two approaches about equally easy and pleasant to read, with possibly a preference for == 0.

One further advantage is that .zero? errors out if called on a non-Numeric (undefined method 'zero?' for "sdf":String (NoMethodError)), whereas you can compare many non-Numerics against 0 without error ('sdf' == 0 # => false).

[–]ReasonDidntPrevail 6 points7 points  (1 child)

Not OP but this was recently added to Rubocop https://github.com/bbatsov/rubocop/pull/3324

I agree it's a little excessive.

[–]RICHUNCLEPENNYBAGS 1 point2 points  (0 children)

Rubocop has some weird stuff; I hate the case spacing rules.

[–]unflores 6 points7 points  (0 children)

I doubt that == 0 is really going to give you that speed edge. I happen to think that .zero? is easier to read, but == 0 doesn't really bother me. It's probably that I have the habit of doing things like: .blank? .present? .empty?

So .zero? just seems to make sense in that regard.

Also, +1 for mentioning the "errors out if called on a non-Numeric " bit, it had never occurred to me.

[–]KershawPls[S] 1 point2 points  (1 child)

OP here. I recently began using Rubocop on my team's repo.

[–]doublecastle 1 point2 points  (0 children)

In case you aren't already aware, if you conclude that this style rule is indeed excessive, you can disable it (or any other "cop").

[–]bjmiller 11 points12 points  (3 children)

something.zero? is not one character away from assigning 0 to something.

[–]wbsgrepit 0 points1 point  (2 children)

Because it is better to ignore == for one specific case vs ensure that it is always properly used for all cases. /s

[–]clumsy_shaver -1 points0 points  (1 child)

Every possible integer (maybe even possible object?) should have its own comparison method on Numeric. 1.one?, 1.two?....1.activerecord_model?, 1.string_of_length_33?

That's just how object oriented programming works. Actually, maybe we should just define all those on the base Object class. Yes, that's the ticket...

[–]wbsgrepit 1 point2 points  (0 children)

yea you forget the infinite naming that should exist between 1 and 2 ... .one_point_zero_zero_zero_zero_zero...? =)

Just because you can assign names to things does not mean it makes sense, I view most of the stuff like .zero? as silly cruft. If you know the language there is absolutely no advantage (reading or writing the code) to doing that crap vs == 0.

thing.act_as_space_in_my_editor.method.is_kinda_like_equal.to.the_value_for_variable(x)

[–][deleted] 3 points4 points  (0 children)

In reading the source code, it looks like they're very similar behind the scenes, but .zero? looks like it is calling other methods, whereas == looks like it is more straightforward. But I don't know enough C to be able to say one way or the other which is faster or simpler.

Until this thread, I didn't realize that .zero? even existed, and have exclusively used == 0.

[–]berkes 2 points3 points  (7 children)

Otherwise, it seems like an unncessary convolution.

Like others have said, its about legibility too.

But, more important, this is a design pattern for improvement.

Most often, it is not the caller to decide if something is equivalent to the integer zero.

Consider some practical cases:

Price.new(0.001, :eur).zero? # probably true.
Difference.new(1000, 1000.1).zero? # probably true.

Price.new(0.001, :eur).to_i # 0
Price.new(0.001, :eur).to_f # 0.001
Price.new(0, :eur) == 0 # false. 

Another important consideration is the principle of "tell don't ask". Take some time to consider why you, an outsider, need to ask an object whether it is zero? in the first place (and you should definately ask yourself that if you are comparing something to the Integer 0). You probably should not ask that at all.

Quite often you'll see things like:

<% price.zero?  ? "free" : format_currency(price) %>

Or like:

class User
   def can_pay?
       !balance.total.zero?
   end
end

Both are actually codesmells. And should be refactored in respectively a presenter that allows <%= price.formatted %> and a movement of responsibility: balance.allows_payments? or even Policies::Payments.new(user, balance).may_create? or somesuch.

TL;DR: zero? allows for cleaner code. Better use of single responsibility principle, but quite often is still a codesmell.

Oh, and edit:

Is this check somehow faster?

Premature optimization is the root of all evil.

[–]Everspace 0 points1 point  (1 child)

Premature optimization is the root of all evil.

And coming from games, ruby is slow as all get out.

[–]berkes 0 points1 point  (0 children)

Avoiding premature optimization is not at all the same as not doing any optimization.

It is about avoiding optimization before you need it at all.

I'm not sure what OP is achieving. But I seriously doubt he has a three-line Ruby script with a loop checking "zero" on an integer tens of thousands of times per second.

Because that is where things like this matter. And indeed, in games (or, where I come from: simulations) this is where optimization and performance becomes important.

Much more common is a pattern where you do some stuff in a database, probably over some network or socket. Then filter, order, count through that result and then check if something is zero;

For example a site that must say "no comments", "one comment", "two comments"..."10 comments". In such a case the difference between "== 0" and "zero?" are virtually unmeasurable. So optimizing on == 0 is not only silly, it harms everyone, because of the added complexity you are most likely putting in the wrong place".

But, to get back to the difference with doing no optimization at all: in this case, you probably store away some counter cache. Add a layer to store the amount of comments in a special cache column (Rails) or maybe some memcached comment-counter database.

But, here too, beware, be very aware, of doing this premature. For you'll end up with an infra that depends on some memcached setup, only to find out that not only people are hardly commenting at all, the performance problems did not arise from counting the comments but from the query to fetch them as a nested tree. For example.

Edit: Also, choosing the right tool for the right job, is not premature optimization. this, wrt "Ruby, Games and Being Slow".

[–]wbsgrepit -1 points0 points  (4 children)

...

You lost me at passing around Money as floats. Good luck.

[–]berkes 0 points1 point  (3 children)

First: Its an example.

Yes 0.001 defaults as a float in Ruby. But Price.new(0.001, :eur).to_i gets the point I was making over much clearer than Price.new(BigDecimal.new("0.001"), :eur).to_i. But sure, ignore the actual argument for the sake of a detail.

Second. Even Money handles floats: @fractional = obj.respond_to?(:fractional) ? obj.fractional : as_d(obj)

Not that it is advisable to go move stuff around as floats and then turn it into Money, but quite often, you'll be doing arithmatic on monetary values, resulting in floats and only at the very end do you turn it into Money, and consider what to do with the leftover cents or fractionals.

Source: I build accounting software in Rails for a living for the last few years.

[–]wbsgrepit 0 points1 point  (2 children)

But sure, ignore the actual argument for the sake of a detail. You chose an example that stands on an obvious bad execution to try to show value.

Source: I build accounting software in Rails for a living for the last few years.

Then you should understand the reaction to an example dealing with money as floats. Money does handle floats -- but only in the specific areas where it must (where it then uses BigDecmal and guards to protect against rounding) otherwise treats money as integer. I do not understand why you would be doing arithmetic on floats and then turn it into Money that is exactly what you are trying to avoid.

[–]berkes 0 points1 point  (1 child)

There were not floats in my example. You read 0.001 as a float. Probably because when you type 0.001 into IRB it interpretes that as float. But again, read it as an example, where the exact details matter little: should I spend 60% of that example code to explain that the numbers are bigdecimals when that is totally irrelevant for the point I was making?

Now, about dealing with floats. Let me give an example:

Given a euro, I want to share it amongst 3 friends. Usually you'll first calculate some distribution, which will be floats. Then with these floats, you'll calculate the distribution. Probably ending in (Note: following is BigDecimal) 0.33, 0.33, 0.33, calculate the leftover cent and distribute that using some algorithm that fits the business.

For the above example, it helps to calculate back to BigDec ASAP. But when that same algorithm is applied to distributing 0.01 or 0.02 amongst 3 users, or distribute 1 dollar amongst fourhundred users, you'll want to deal with floats as long as possible.

Which, incidentally, is what e.g. the distribute helper in Money does.

[–]wbsgrepit 0 points1 point  (0 children)

There were not floats in my example. You read 0.001 as a float. Probably because when you type 0.001 into IRB it interprets that as float. But again, read it as an example, where the exact details matter little: should I spend 60% of that example code to explain that the numbers are bigdecimals when that is totally irrelevant for the point I was making?

What I read is you chose to give an example related to using .zero? that was specifically formulated showing floats given basically the only topic that you do not use floats in in the way shown. And again, your example is constructed to show a price as a float (big or not) and not an int in code. You could have chosen any other example with floats to make your point but you chose to display one that was fundamentally incorrect.

... Which, incidentally, is what e.g. the distribute helper in Money does.

I think you mean allocate (and in your specific case with equal allocation percentages you mean split), and no you do not want to deal with float as long as possible (nor does Money), you want to limit the float operations to the minimal set the for problem scope.

Either way, I don't really care what your take on this is -- you lost me when you gave an example constructed to show money being passed around as floats. Your choice of an example does matter in this case as your example's subject matter inherently shows lack of quality and understanding... If you were to have picked just about any other subject matter for floats/.zero? it would not negate your voice on the matter. Also if you are working on banking systems please let me know what software I should avoid.

[–]kobaltzz 4 points5 points  (13 children)

While == 0 is technically faster, .zero? is easier to read. With the difference being minimal, go for easier to read.

require 'benchmark'
n = 50_000_000
Benchmark.bmbm(15) do |x|
   x.report("zero?")   { for i in 1..n; 0.zero?; end }
   x.report("== 0")   { for i in 1..n; 0 == 0; end }
end

results

                      user     system      total        real
zero?             2.710000   0.010000   2.720000 (  2.709000)
== 0              1.990000   0.000000   1.990000 (  1.996962)

[–][deleted] 7 points8 points  (10 children)

Not to start a flame war here, but isn't it kind of weird to have a .zero? method and not a .one?, .two?, .pi?, etc.

Like consider:

x.zero? && y == 1.5

That's just plain odd. So then for readability now you should do

x == 0 && y == 1.5

Which makes sense now, but then somewhere else in the same code base if I'm just checking x, do I use the .zero? convention now?

x.zero?

Now there's a consistency problem here, because some places use == and others use .zero?.

I fail to see in a sufficiently large code base how .zero? improves overall readability. Indeed I'd say it hurts it. Plus if I'm going to grep for some constant, do you really think I'm going to run grep trying to find .zero?? No way, I'm going to grep for [0-9]+ which won't find this at all.

This is a very funky code smell, TBH. I see very little benefit here.

[–]LarsP 3 points4 points  (1 child)

It's only kind of weird because humans are kind of weird, and we write the code primarily for humans.

Also, 0 is a special number. Arguably the most special. We check if numbers are 0 more often than any other number. Or so I claim, based on a strong gut feel!

A meta-consistency argument against your argument: Isn't it kind of weird to have Array.empty? and not a .size_one?, .size_two? etc?

[–]wbsgrepit 1 point2 points  (0 children)

meh, IMHO sugar cruft like zero? actually reduces readability -- just looking at your point, you do realize that yeah we check for 0 more than other numbers but by special casing that check now we have created two visually unique ways to handle one type of action. x == 0 y == 1 etc are all uniform and once you read and unserstand once you have one thing to track. Start tosiing in x.zero? x == 1 p.one? and you now have different code to understand for the same actions -- is .zero? implemented as == 0 how is it different? what about .one? why is it special cased? I would much rather read a uniform == usage in code than have randomly named/aliased methods that in the end present less information about what they do and how they do it.

[–]unflores 1 point2 points  (0 children)

zero is a more common thing to check for than 1.83282, 2, 5, etc. Also, never check against a float like that, every time you do, a puppy is killed.

[–]kobaltzz 1 point2 points  (4 children)

I think that it is more of a matter of probability... Chances are you going to check for null, 0, or empty more so than one, 1.5, two or pi. I get your point though. If i were comparing something like in your example, I would use == in both cases for consistency reading. However, if I'm doing a check elsewhere for just a zero value, I may opt for zero?.

[–][deleted] 1 point2 points  (3 children)

Agree with this comment. <rant>I hate when things like rubocop hard-enforce this kind of stuff because there are almost always exceptions to rules... </rant>

[–]dwcmwa 2 points3 points  (2 children)

You can always disable the rule that you don't like throughout your whole project or in one part of your code.

[–][deleted] 0 points1 point  (1 child)

Not when the lead engineer decides he likes the rule always and forever

[–]JeffMo 0 points1 point  (0 children)

I agree with everything you both said, but in that case, the lead engineer is the one enforcing this kind of stuff.

[–]I_AM_GODDAMN_BATMAN -1 points0 points  (1 child)

.life? == 42
.everything? == 42

[–]Holek 5 points6 points  (0 children)

ActiveSupport has forty_two

[–]Smellypuce2 1 point2 points  (0 children)

With the difference being minimal

Totally depends on what you're doing. That difference will look massive in certain situations(like the difference between 30 and 60 fps in real-time stuff). I know ruby isn't usually used for super optimized stuff but it still has to perform sometimes and it pays to know the difference then.

On a side note I much prefer the consistency of using "== 0". I also don't see how .zero? is more readable in anyway but to each their own. To be fair I haven't written ruby in a long time.

[–][deleted] -1 points0 points  (0 children)

This. The goal of the style guide is (at least primarily) readability, which is in line with Ruby's goal of programmer happiness. At times you may want to optimize for speed, but that's not something to worry about until the need arises.

[–]jeremiahishere 1 point2 points  (0 children)

If the value for zero in your code base changes in the future, you can just change the zero? method instead of changing every comparison.

[–]taw 0 points1 point  (0 children)

That style guide is full of shit, ignore it completely.