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

(re)implement equality with 2pi #22

Closed
waldyrious opened this issue May 14, 2017 · 14 comments
Closed

(re)implement equality with 2pi #22

waldyrious opened this issue May 14, 2017 · 14 comments

Comments

@waldyrious
Copy link
Collaborator

I have been collecting some loose notes about this but hadn't posted a plan publicly as I didn't have a clear plan of how to go about tackling this. But given @giordano's recent involvement with the package (and with the overall handling of Irrational in julia), I feel the time is right to move ahead and make a decision.

As the README indicates, this package was originally meant to provide the tau == 2pi equality (which should by definition be true). This is not possible at the moment due to the * operator implicit in 2pi converting the result of that expression to a float, which Irrationals are explicitly defined as not equal to. However, since this package was created explicitly to give special meaning to the value of 2pi, it would make sense for it to ensure that 2pi handles as the same Irrational as well. This may not be in scope for base Julia (@StefanKarpinski often expresses his concern that at some point these changes will drift towards implementing a full-fledged math parsing engine or CAS), but it sounds reasonably in scope for a package like this.

One way of doing so could be using Julia's core feature of multiple dispatch to override the * operator for the case where the operands are pi and 2, as I suggested a while ago in this thread. There is even some precedent for such special cases in base julia: exp.jl, for instance, has a special case for exp(1), implemented using a simple if test.

Instead of an if test, I have been looking at Value types to implement this. PatternDispatch could also provide inspiration for possible alternative approaches. These options may be overkill for the single 2pi case, but may be adequate if we want something more generic.

Indeed, a more general solution could be more appropriate: integer multiples of pi (or tau) should, for semantic reasons, be handled with the same manner as pi/tau itself. @simonbyrne suggested such a PiMultiple type in the PR that introduced sinpi/cospi, and I think the Tau package could make that experiment with a TauMultiple type.

Ok, now that I made the case, I'm curious about any arguments against. In particular, I'm wondering why @giordano considers that "it's exactly the fact that tau isn't the same as 2 * pi that makes this package particularly useful."

@giordano
Copy link
Member

giordano commented May 14, 2017

One way of doing so could be using Julia's core feature of multiple dispatch to override the * operator for the case where the operands are pi and 2,

This would be an act of type piracy of types defined in Julia's standard library, which is considered bad practice: https://docs.julialang.org/en/latest/manual/style-guide.html#Avoid-type-piracy-1

I don't see why it's a problem that tau != 2 * pi. They're numerically equivalent, but 2 * pi is a Float64 number, while tau is an Irrational with some nice features, like the ability to be automatically promoted to a BigFloat with full precision when used in calculations involving other Big* numbers. This isn't possible with 2 * pi, that's why I made the statement quoted above ;-)

@waldyrious
Copy link
Collaborator Author

I understand the concern, which I agree is serious in the generic case (e.g. if done for types that are completely distinct from each other) but given auto-promotion rules for Irrationals, I don't see how treating 2pi as an Irrational could cause real-world problems.

Aside from the conceptual "sin" (which has to be balanced with the IMO more serious mathematical "sin" of defining tau != 2pi), what kind of problems do you foresee for users of this package?

@simonbyrne
Copy link
Member

If I have a function:

foo(n) = n*pi

then the result will be type unstable.

@waldyrious
Copy link
Collaborator Author

waldyrious commented May 15, 2017

I understand that there will be type instability, but can it actually cause bugs in this particular case, or only performance degradation? I mean, I don't think there's any functional distinction between, say, using 2*pi vs. pi in Julia code, except in the optimization of the generated code, or am I missing something?

Otherwise, would the PiMultiple/TauMultiple approach be preferable?

@giordano
Copy link
Member

I understand that there will be type stability, but can it actually cause bugs in this particular case, or only performance degradation?

Most of performance of Julia codes comes from writing type-stable code. Disrupting this principle in a simple (and also common) operation like n * pi, with n generic Number or even Real, might have bad performance effects.

Tau.jl does a few simple things, and does them well: it provides a constant (actually an Irrational instance) and a few optimized functions like sintau/costau (but remember that sintau/costau with Real argument can be much improved, see #19 (comment)), that are handy, because the constant 2π is so ubiquitous and operations like sin(2πx) are very common, as stated in the Tau Manifesto.

Making 2 * pi exactly the same thing as tau is technically possible, but goes beyond the scope of this package and has no real gain, apart from bad performance, and I guess your idea was instead to improve performance ;-)

@waldyrious
Copy link
Collaborator Author

waldyrious commented May 18, 2017

You certainly gave me food for thought. After mulling this over, I think I'll agree that we shouldn't make tau == 2pi, but for slightly different reasons than type stability / performance alone.

See, I was hoping one could do using Tau and every instance of 2pi would automatically behave as tau, without having to manually convert one's code, but I can see how that's not really a realistic requirement, and in fact could be even counterproductive to the "tauist cause": if one chooses to use the Tau package, it would make sense for them to take the extra step to change their code to explicitly reflect their choice of circle constant, rather than leaving the 2pi's around.

With that in mind, though, I think it would be nice to provide some sort of warning for when a downstream package uses 2pi in its code -- which could be done by overriding *(2, pi), without changing its resulting type. I'm not sure if it's possible to distinguish code from a module from code from modules it imports (we wouldn't want to warn a user of instances of 2pi in packages they didn't author);

If that distinction is not possible (or the warning approach found too intrusive), then an alternative could be a helper script or method in this package that would process a given module or file and output a list of locations where 2pi could be replaced by tau. This is easy enough to do by hand, but depending on the search method, one could easily miss valid instances -- 2pi, 2*pi, 2 * pi, 2.0 * pi, pi*2, etc. -- so an automated process might be useful.

What do you think?

@giordano
Copy link
Member

Even if type piracy weren't a concern (but it is)...

See, I was hoping one could do using Tau and every instance of 2pi would automatically behave as tau, without having to manually convert one's code, but I can see how that's not really a realistic requirement, and in fact could be even counterproductive to the "tauist cause": if one chooses to use the Tau package, it would make sense to change the code to explicitly reflect the choice of circle constant, rather than leaving the 2pi's around).

...with which goal? Sorry, I don't see what you expect to gain with such auto-promotion.

If that distinction is not possible (or the warning approach found too intrusive), then an alternative could be a helper script or method in this package that would process a given module or file and output a list of locations where 2pi could be replaced by tau. This is easy enough to do by hand, but depending on the search method, one could easily miss valid instances -- 2pi, 2*pi, 2 * pi, 2.0 * pi, pi*2, etc. -- so an automated process might be useful.

What do you think?

I don't know, I don't have a strong opinion on this. In any case, I wouldn't make it part of the package itself, at least an extra script to be placed outside src directory.

@waldyrious
Copy link
Collaborator Author

waldyrious commented May 19, 2017

...with which goal? Sorry, I don't see what you expect to gain with such auto-promotion.

I'm afraid I didn't make myself clear. I am already on board with not doing the auto-promotion. I was just explaining what was my thinking for proposing that initially, not advocating for that -- which I meant to make clear when I said:

I can see how that's not really a realistic requirement, and in fact could be even counterproductive

I edited my comment above to hopefully clarify I was talking about the users of Tau.jl making the 2pi --> tau change, rather than Tau.jl doing that under the hood.

Also, I didn't mean to imply that I don't believe type piracy is a serious concern, just that I tend to value users expectations of a tool (in this case that tau == 2pi) more than the tool's technical requirements, e.g. performance-wise (I take a similar stance on the discussion about Julia's overflow behavior, for example). I'm just giving you context, not making a case for anything :)

In any case, I wouldn't make it part of the package itself, at least an extra script to be placed outside src directory.

Ok. I'll open a separate issue to track the creation of such a script.

@waldyrious
Copy link
Collaborator Author

Issue created at #24.

@giordano
Copy link
Member

I edited my comment above to hopefully clarify I was talking about the users of Tau.jl making the 2pi --> tau change, rather than Tau.jl doing that under the hood.

Good, much better now :-)

@waldyrious
Copy link
Collaborator Author

Indeed, a more general solution could be more appropriate: integer multiples of pi (or tau) should, for semantic reasons, be handled with the same manner as pi/tau itself. @simonbyrne suggested such a PiMultiple type in the PR that introduced sinpi/cospi, and I think the Tau package could make that experiment with a TauMultiple type.

For future reference, a similar possibility was also raised by @martinholters in JuliaLang/julia#7994 (comment):

According to ?one, one(π) should give "the multiplicative identity element for the type of" π. I don't think we have that at the moment (1*π == 1.0*π ≠ π). That would leave introducing Irrational{1} (or maybe a more appropriately named singleton type, say One) with correspondingly overloaded *. I'm not sure we want to go down that road, though.

@giordano
Copy link
Member

How is an "irrational 1" better than the integer 1? In any case Irrational{1} wouldn't have the same type as pi or any other Irrational, so the argument of having the same type doesn't hold.

@waldyrious
Copy link
Collaborator Author

waldyrious commented May 31, 2017

Agreed. I wasn't arguing for that particular idea, just making a cross-reference for posterity, since it also mentions the potentially unexpected inequality arising from multiplying an Irrational by 1, and a possible override of *.

@martinholters
Copy link

Yes, to clarify: My point was that presently, there is no multiplicative identity for π (or any other Irrational), no matter the type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants