Closed michaelpj closed 2 years ago
Real
: if the type also implementsFractional
, thenfromRational
is a left inverse fortoRational
, i.e.fromRational (toRational i) == i
.Fractional
:toRational
is total, and if the type also implementsReal
, thenfromRational
is a left inverse fortoRational
, i.e.fromRational (toRational i) == i
.
It seems that NaN
is the usual exception here:
ghci> toRational (0 / 0 :: Float)
(-510423550381407695195061911147652317184) % 1
ghci> fromRational $ toRational (0 / 0 :: Float) :: Float
-Infinity
I think that's acceptable though. Many Float
and Double
instances already have documentation like this example from Fractional
:
Note that due to the presence of NaN, not all elements of Double have an multiplicative inverse.
>>> 0/0 * (recip 0/0 :: Double) NaN
Reddit discussion: https://www.reddit.com/r/haskell/comments/umtbqp/adddocument_laws_for_tointeger_and_torational/ See also https://ro-che.info/articles/2019-05-14-convert-cdouble-to-double
@michaelpj I suggest amending the law to say "if i == i
then fromRational (toRational i) == i
". This is motivated to work around NaN, but makes sense on its own: if your data type is so crazy that i /= i
clearly you cannot expect f (inverse_f i) == i
.
@Bodigrim I think I prefer @sjakobi 's suggestion of just adding a note about the instances for Double
. Adding a precondition "if i==i
" seems like false generality to me: that is pretty much just a check that "this is not NaN", and I don't see any reason to think that it's an appropriate precondition in other cases. Better to just add to the big list of caveats on Double
IMO.
@michaelpj fair enough.
Dear CLC members, let’s vote on the proposal adding laws to fromInteger
/ toInteger
and fromRational
/ toRational
as described in the top post. @tomjaguarpaw @cgibbard @mixphix @emilypi @chessai
+1 from me.
+1, including the comment about the instance for Float/Double.
+1, including the comment about the instance for Float/Double.
+1 to this as well
+1
+1
+1
Approved unanimously. @michaelpj could you please create an MR?
@michaelpj just a gentle ping to raise an MR, so that I approved it.
I'm trying to summarise the state of this proposal as part of my volunteering effort to track the progress of all approved
CLC proposals.
Field | Value |
---|---|
Authors | @michaelpj |
Status | merged |
base version |
4.18.0.0 |
Merge Request (MR) | https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8496 |
Blocked by | nothing |
CHANGELOG entry | missing (please, raise an MR to base to update changelog) |
Migration guide | not needed |
Please, let me know if you find any mistakes 🙂
Motivation
In the process of writing https://github.com/haskell/core-libraries-committee/issues/40, I realised that the pairs of typeclass methods
toInteger
/fromInteger
andtoRational
/fromRational
actually do have some reasonable coherence laws that I expect everyone assumes. Nonetheless, it would be good to document these.Proposal
Add the following laws to the documentation of the following typeclasses:
Num
: if the type also implementsIntegral
, thenfromInteger
is a left inverse fortoInteger
, i.e.fromInteger (toInteger i) == i
.Integral
:toInteger
is total, andfromInteger
is a left inverse for it, i.e.fromInteger (toInteger i) == i
.Real
: if the type also implementsFractional
, thenfromRational
is a left inverse fortoRational
, i.e.fromRational (toRational i) == i
.Fractional
:toRational
is total, and if the type also implementsReal
, thenfromRational
is a left inverse fortoRational
, i.e.fromRational (toRational i) == i
.Note: we cannot say anything about the right inverse properties, because there is no way of telling when
fromInteger
/fromRational
is outside it's range of precise functioning: it may be bottom or it may just return a different answer.Discussion
Are these good laws?
The main question is: are there instances that don't satisfy this? I made an effort here to check this. As far as I can tell, all of the main instances in
base
satisfy these laws. It's of course possible that instances in user code do not.Indeed, for the "central" use case of providing an isomorphism to a subset of the integers/rationals, it seems hard to question that these laws should hold. However, there may be other use cases that I haven't thought of where you might want to violate them!
The most plausible law that I could imagine being broken is the totality of
toInteger
/toRational
, in case the isomorphism is only between subsets of both types.Where should the documentation go?
For
Num
/Integral
, sinceNum
is a superclass ofIntegral
, we could put the documentation onIntegral
, since that is where we know that we have both instances. However forReal
andFractional
neither is a superclass of the other. As such, I opted for a consistent approach of putting the documentation on both typeclasses, including conditional language if necessary.