Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zero? to Number, Time::Span, and Complex #4026

Merged
merged 5 commits into from
Jun 23, 2017

Conversation

danini-the-panini
Copy link
Contributor

No description provided.

@refi64
Copy link
Contributor

refi64 commented Feb 12, 2017

But...why? x.zero? is even a character longer than x == 0, so I'm not seeing any real benefit...

@0x1eef
Copy link

0x1eef commented Feb 12, 2017

@kirbyfan64 it's a Rubyism.

@bcardiff
Copy link
Member

The only benefit I see in ruby is to use it in a map / filter. But in crystal it is possible to do

[1,0].map &.==(0) # => [false, true]

@ysbaddaden
Copy link
Contributor

Not as pretty as ary.reject(&.zero?) but more flexible since it can take any value.

@danini-the-panini
Copy link
Contributor Author

danini-the-panini commented Feb 12, 2017

I think it's more "human readable"

e.g.
do_the_thing until value.zero?

as opposed to
do_the_thing until value == 0

I ran into it while converting some Ruby code into Crystal so I thought I'd add it. It also might save some Rubyists from a few surprises as well.

EDIT I agree with @ysbaddaden. IMO it's prettier to write ary.reject(&.zero?) than ary.reject &.==(0). It also "reads" more like English.

@makenowjust
Copy link
Contributor

@jellymann There is Crystal land, so you need to learn Crystal culture.

And, I don't think zero? is bad idea when I call Number.zero and another T.zero to mind (T is a class having zero class method). The predicate to check equality with T.zero should be named zero?, and it can't simply replace x == 0 (Of course, we can replace it as x == typeof(x).zero). In fact Time::Span has zero class method and its instance can't compare with 0. You should approach it with Time::Span#zero?.

@danini-the-panini danini-the-panini changed the title Add zero? to Number Add zero? to Number and Time::Span Feb 13, 2017
@danini-the-panini danini-the-panini changed the title Add zero? to Number and Time::Span Add zero? to Number, Time::Span, and Complex Feb 13, 2017
src/time/span.cr Outdated
@@ -286,6 +286,10 @@ struct Time::Span
def self.zero
new(0)
end

def zero?
self == typeof(self).zero
Copy link
Contributor

@Sija Sija Feb 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not simply @ticks == 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read @makenowjust's comment. This is a more general approach, comparing directly to the type's definition of "zero"

Copy link
Contributor

@Sija Sija Feb 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "more general approach" needlessly allocates new object on the stack, while actually you only need to compare the @ticks ivar here. There's a reasoning behind it in relation to Number since it may hold different values not always comparable with 0 — as Int32. It does not apply here at all though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's a general implementation, then it should be on a more general type.

Copy link
Contributor

@Sija Sija Feb 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how Time::Span is a general type. There's Time::Span::Zero constant more suitable for this anyhow.

Copy link
Contributor

@RX14 RX14 Feb 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sija I was agreeing with you.

self == typeof(self).zero is a general implementation because it works on all types with a self.zero implementation. I was saying that there's no point in using a more general implementation like this versus @ticks == 0, unless you move #zero? to Object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so there's no point in doing self == typeof(self).zero if it's not on a general type. Number is a general type, so it's probably okay to leave it. Time::Span and Complex could probably be more specific, though, since they're concrete types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we create a Zeroable module that we just include on every type that has a self.zero implementation? That also allows people to juste drop in Zeroable on their own classes if they need to.

e.g.

class Vector3
  def self.zero
    new 0, 0, 0
  end
  include Zeroable
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could work, although it seems a bit verbose to have a module just for that.

src/number.cr Outdated
# 5.zero? #=> false
# ```
def zero? : Bool
self == typeof(self).zero
Copy link
Contributor

@Sija Sija Feb 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not simply self.to_f == 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read @makenowjust's comment. This is a more general approach, comparing directly to the type's definition of "zero"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For struct Number is ok the typeof(self).zero since the literal expanded would be of exact type and that is better. But on Complex and Timespan I would say to go for the plain/@ivar == approach. The compiler will end up inlining and simplifying it, but I think is simpler the @ivar == and we avoid the extra work in the optimization.

@danini-the-panini
Copy link
Contributor Author

@kirbyfan64 Is Crystal's goal to save keystrokes?

You’d think that a language inspired by Ruby would actively embrace Rubyisms.

@ysbaddaden
Copy link
Contributor

We don't want to copy every rubyism. For example we avoid aliases and prefer a single way or method to do something. To add a new feature into the core/stdlib we need to make sure it has a real, added, value.

@JacobUb
Copy link
Contributor

JacobUb commented Feb 17, 2017

One benefit of this would be slightly more type safety because in x == 0, "x" could, by mistake, be of some type with no meaningful comparison to a number and x.zero? would throw a compiler error instead.

@bcardiff
Copy link
Member

Since using #zero? is funner and I see no cons on adding this I am open to include it.

Yet, if an implementation is supposed to receive a number (scalar or complex), checking with == 0 is enough. Even if typeof(0) != typeof(number). So the only value of adding this is to have some unified way to distinguish numbers and timespans zeros. If anyone can highlight a benefit for this it might help on others to 👍 or 👎 .

@danini-the-panini
Copy link
Contributor Author

danini-the-panini commented Mar 25, 2017

I'd just like to say, another reason why I think this is a good fit is that Int already has even? and odd?, and I think zero? fits well into that group of functions.

Also, Complex#zero? is easier than x.real == 0 && x.imag == 0, and more concise than x == Complex.zero. Similarly for time spans, consider x.ticks == 0 or x == Time::Span.zero vs x.zero?. I think it's pretty good reason for zero? to exist, and in going with POLA, Number ought to have it as well.

@ozra
Copy link
Contributor

ozra commented Mar 28, 2017

I also think zero? makes sense (as an "almost anti ruby" dude at that). Zero is not "just another number". And as already mentioned, in generic code one call covers a bunch of ugly situations. I've used the corresponding thing in C++ for generic containers/readers etc. [ed: for some rather specialized datatypes where 0 still "is a thing"] and I imagine I'll stumble in those directions in Crystal too. +1.

@bew
Copy link
Contributor

bew commented Jun 9, 2017

Bump, what's the state of this PR? (the build error is about file formatting)

@akzhan
Copy link
Contributor

akzhan commented Jun 14, 2017

I vote for this pull request.

Zero has special meaning, and need zero? just like infinity? and nan?.

Formatting should be updated due to changes in crystal formatter itself.

@bcardiff
Copy link
Member

@jellymann are you available to fix the formatting so this can be merged?

Removed generic implementation of `zero?` to avoid unecessary
optimisation.
@danini-the-panini
Copy link
Contributor Author

@bcardiff I've fixed the formatting issues, but the Travis build is still failing for some reason.

@Sija
Copy link
Contributor

Sija commented Jun 22, 2017

@bcardiff C'mon, lets merge #4528...

@bcardiff bcardiff merged commit a431a57 into crystal-lang:master Jun 23, 2017
@bcardiff
Copy link
Member

Thanks @jellymann!

@bcardiff bcardiff added this to the Next milestone Jun 23, 2017
@danini-the-panini danini-the-panini deleted the patch-1 branch June 23, 2017 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.