rhannequin / astronoby

Ruby library based on astronomy and astrometry books
https://dev.to/rhannequin/series/17782
MIT License
59 stars 2 forks source link

Arithmetic methods support for `Angle` #16

Closed rhannequin closed 9 months ago

rhannequin commented 9 months ago

This introduces several arithmetic functions in Angle to increase readability and developer experience.

Instance methods

Class methods

Examples

angle_1 = Astronoby::Angle.as_radians(described_class::PI)
angle_2 = Astronoby::Angle.as_degrees(45)
new_angle = angle_1 + angle_2
new_angle.degrees
# => 225

Astronoby::Angle.as_radians(Math::PI / 6).sin
# => ~ 0.5

angle = Astronoby::Angle.atan(1)
angle.radians
# => ~ π÷4
rhannequin commented 9 months ago

@JoelQ

def +(other)
  self.class.as_radians(radians + other.radians)
end

This implementation doesn't allow for arithmetic operations with regular numbers:

Astronoby::Angle.as_degrees(180) + 60
# `+': undefined method `radians' for 60:Integer (NoMethodError)

I need to do the following:

angle = Astronoby::Angle.as_degrees(180)
Astronoby::Angle.as_degrees(angle.degrees + 60)

Do you think it would be worth it to allow somehow this?

angle = Astronoby::Angle.as_degrees(180)
new_angle = angle + 60
new_angle.degrees # => 240

This would be very convenient to write, but also very error prone. I don't even know how to do it because angles are stored in radians and the #+ method would have no clue what the numeric value's unit would be.

This reminds me of your suggestion to implement #cos, but this means I have to be cautious because #cos would return a number (implicitly in radians) instead of an Angle.

Do you have an opinion?

JoelQ commented 9 months ago

This implementation doesn't allow for arithmetic operations with regular numbers [...] This would be very convenient to write, but also very error prone. I don't even know how to do it because angles are stored in radians and the #+ method would have no clue what the numeric value's unit would be.

Agreed! IMO you shouldn't do arithmetic operations with raw numbers. Raw numbers are ambiguous and lead to bugs. Consider:

angle = Angle.as_degrees(180)
angle + 60

What does 60 represent? Is it an angle in degrees? An angle in radians? Some other quantity altogether such as distance in meters? A dimensionless number?

It's safer and easier to read if you do write this as:

angle = Angle.as_degrees(180)
other_angle = Angle.as_degrees(60)

new_angle = angle + other_angle

In general

This reminds me of your suggestion to implement #cos, but this means I have to be cautious because #cos would return a number (implicitly in radians) instead of an Angle.

Interestingly, the value returned by trigonometric functions is not a number of radians. It is not even an angle. Instead it is a unit-less number (a ratio of two distances so the units cancel out)

The idea that you can do math between and across unit types, that this can result in new units, and that some operations are forbidden leads to the technique of dimensional analysis, where a calculation can be spot-checked for correctness by making sure that the units that come out of it are those expected

Some languages like F# can track unit math automatically for you, which is pretty cool!