
Hi folks, I have uploaded a new package to Haskell: convertible. At its heart, it's a very simple typeclass that's designed to enable a reasonable default conversion between two different types without having to remember a bunch of functions. The return type from this conversion is "Either ConvertError a", and conversions are expected to do sanity checking (such as bounds checking when converting to types like Int), so as to produce neither garbage nor exceptions as part of the conversion process. The package also includes instances of the Convertible typeclass for working with numeric types as well as dates and times. Notably, it has code to convert between System.Time types and their Data.Time siblings, and vice versa, a capability I found annoyingly lacking in the standard library. There is also a simple wrapper function called convert, that transforms a Left result into a call to error, and returns a Right result. For HDBC v2.0, the SqlValue/SqlType system has been completely rewritten in terms of Convertible, though I expect backwards compatibility will not be broken by this change (existing code will compile and run fine). I hope to release v2.0 of the HDBC API and backends in a few days. Convertible is not for everyone; if you care whether you use truncate or round to convert a Double to an Integer, this isn't for you. Note, though, that you can import the library without its default instances, so you can define your own if you prefer. API docs and downloads at: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/convertible -- John

John Goerzen wrote:
Hi folks,
I have uploaded a new package to Haskell: convertible. At its heart, it's a very simple typeclass that's designed to enable a reasonable default conversion between two different types without having to remember a bunch of functions.
I once again point out that realToFrac is *wrong* for converting from Float or Double. > realToFrac (1/0::Float) ::Double 3.402823669209385e38 > > realToFrac (0/0::Float) ::Double -5.104235503814077e38 > realToFrac (0/0::Double) ::Float -Infinity > realToFrac (1/0::Float) ::Rational 340282366920938463463374607431768211456%1 > > realToFrac (0/0::Float) ::Rational (-510423550381407695195061911147652317184)%1 > realToFrac (1/0::Double) ::Rational 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216%1 > > realToFrac (0/0::Double) ::Rational (-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824)%1 These exceptional values are not uncommon and should be dealt with correctly. The code to do this is already written in logfloat:Data.Number.Transfinite[1], simply use the realToFrac method of the RealToFrac class instead of the Prelude's version which is broken. Since there is no total conversion from Float/Double to Rational, you can use the isInfinite and isNaN members of the Transfinite class to capture exceptional values as errors. [1] http://hackage.haskell.org/cgi-bin/hackage-scripts/package/logfloat -- Live well, ~wren

On Tue, Jan 27, 2009 at 09:41:30PM -0500, wren ng thornton wrote:
John Goerzen wrote:
Hi folks,
I have uploaded a new package to Haskell: convertible. At its heart, it's a very simple typeclass that's designed to enable a reasonable default conversion between two different types without having to remember a bunch of functions.
I once again point out that realToFrac is *wrong* for converting from Float or Double.
> realToFrac (1/0::Float) ::Double 3.402823669209385e38
Yes, I understand what you are saying and agree with you. But there is nothing better in the standard library, and I did not feel it was worth adding another dependency to the package simply for the sake of this sort of thing. People that need it can get it for themselves and write their own instance of Convertible if they wish. I also note that fromIntegral does no bounds checking and produces garbage in a number of cases. This, though, is detected in convertible due to bounds checking there.
These exceptional values are not uncommon and should be dealt with correctly. The code to do this is already written in logfloat:Data.Number.Transfinite[1], simply use the realToFrac method of the RealToFrac class instead of the Prelude's version which is broken.
I wonder if you would consider submitting a patch to base? It seems that this is a sore problem there, and ideally should be dealt with properly in base. -- John

John Goerzen wrote:
wren ng thornton wrote:
I once again point out that realToFrac is *wrong* for converting from Float or Double.
> realToFrac (1/0::Float) ::Double 3.402823669209385e38
Yes, I understand what you are saying and agree with you. But there is nothing better in the standard library, and I did not feel it was worth adding another dependency to the package simply for the sake of this sort of thing. People that need it can get it for themselves and write their own instance of Convertible if they wish.
Then add a wrapper that calls Prelude.isNaN and Prelude.isInfinite in order to guard against these values just like you do for conversion from unbounded types to bounded types. Avoiding a trivial dependency is no excuse for avoiding correctness.
These exceptional values are not uncommon and should be dealt with correctly. The code to do this is already written in logfloat:Data.Number.Transfinite[1], simply use the realToFrac method of the RealToFrac class instead of the Prelude's version which is broken.
I wonder if you would consider submitting a patch to base? It seems that this is a sore problem there, and ideally should be dealt with properly in base.
As Bertram Felgenhauer says, it's not as easy as fixing base; the problem is an error in the Haskell98 specification. The Float and Double types contain exceptional values which cannot be represented in Rational (by definition). The only Haskell98 solution is to raise an error when attempting to convert those values into Rational, which isn't much of an improvement. At some point I may propose the logfloat solution for Haskell Prime. MPTCs have already been accepted, but the logfloat solution does necessitate additional non-insignificant changes. For example there should be a class for partially ordered types[1][2]. To reduce code bloat a PartialOrd instance should be available whenever an Ord instance is; which means opening the whole can of worms about changing the numeric class hierarchy and changes to the type-class mechanism in order to simplify defining all those different instances. So far I've held off on proposing the solution because I haven't worked out the best way to resolve these issues with minimal changes. [1] The Ord instance for Float and Double is also wrong, since NaN means there's no total ordering (and the existence of NaN is necessitated by the existence of Infinity). In addition to the fact that partial orderings are more common than total orderings, this means we should have a partial ordering class anyways. [2] There's also the issue of what type the partial and total comparison functions should return. Logfloat uses Maybe Ordering to make use of the various helper functions and type classes defined on those types, but for performance reasons we would much rather prefer a flat type. It's inelegant to have both Ordering and PartialOrdering types; and it's hackish to just have PartialOrdering and have Ord ensure that the NotComparable value is never generated. -- Live well, ~wren

wren ng thornton wrote:
John Goerzen wrote:
I once again point out that realToFrac is *wrong* for converting from Float or Double.
> realToFrac (1/0::Float) ::Double 3.402823669209385e38 Yes, I understand what you are saying and agree with you. But there is nothing better in the standard library, and I did not feel it was worth adding another dependency to the package simply for the sake of
wren ng thornton wrote: this sort of thing. People that need it can get it for themselves and write their own instance of Convertible if they wish.
Then add a wrapper that calls Prelude.isNaN and Prelude.isInfinite in order to guard against these values just like you do for conversion from unbounded types to bounded types.
Avoiding a trivial dependency is no excuse for avoiding correctness.
"trivial"? logfloat doesn't appear to even try to work with Hugs. If I depend on logfloat, I break compatibility with Hugs. If I don't, I get behavior no worse than people are used to from base in the default instances. Moreover, it adds yet another dep that people have to resolve if they're not using cabal-install, and yet another library that could break upstream and wind up breaking my code. I do ship some helpful default instances with convertible, but the point of the package is more to provide a standard infrastructure for adding your own instances, as HDBC does. I think you are blowing this way out of proportion. As for calling isNaN, that may make some sense, though it appears it would not fix the problem on Hugs, as you recently pointed out. Though if isNaN works on a float or double, then we ought to be able to construct NaN on the other end rather than cause an error. That's probably reasonable.
As Bertram Felgenhauer says, it's not as easy as fixing base; the problem is an error in the Haskell98 specification. The Float and Double
That doesn't mean you can't add the LogFloat modules to base. Adding those modules shouldn't break what Haskell98 mandates, right? -- John

John Goerzen wrote:
wren ng thornton wrote:
John Goerzen wrote:
wren ng thornton wrote:
I once again point out that realToFrac is *wrong* for converting from Float or Double.
Yes, I understand what you are saying and agree with you. But there is nothing better in the standard library, and I did not feel it was worth adding another dependency to the package simply for the sake of this sort of thing. People that need it can get it for themselves and write their own instance of Convertible if they wish.
Then add a wrapper that calls Prelude.isNaN and Prelude.isInfinite in order to guard against these values just like you do for conversion from unbounded types to bounded types.
Avoiding a trivial dependency is no excuse for avoiding correctness.
"trivial"?
It only depends on base (the new smaller one even) and common language extensions which are supported by Hugs (CPP, FlexibleContexts, OverlappingInstances, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses). Granted, Yhc and nhc98 are out of the running because of MPTCs.
logfloat doesn't appear to even try to work with Hugs.
Works fine for me... $> hugs -98 +o -F'cpp -P' Data/Number/LogFloat.hs __ __ __ __ ____ ___ _________________________________________ || || || || || || ||__ Hugs 98: Based on the Haskell 98 standard ||___|| ||__|| ||__|| __|| Copyright (c) 1994-2005 ||---|| ___|| World Wide Web: http://haskell.org/hugs || || Bugs: http://hackage.haskell.org/trac/hugs || || Version: September 2006 _________________________________________ Hugs mode: Restart with command line option +98 for Haskell 98 mode Type :? for help Data.Number.LogFloat> Granted, I just found some bugs due to bugs in Hugs regarding exceptional values. These will be fixed in the next release.
I do ship some helpful default instances with convertible, but the point of the package is more to provide a standard infrastructure for adding your own instances, as HDBC does. I think you are blowing this way out of proportion.
I think you undervalue the importance of correct code. Many programmers beside myself use Haskell because it guarantees more-correct code than other languages. Correctness is expecially important for "standard infrastructure". Not providing instances is better than providing incorrect instances. Given all the other instances for Int, Integer, etc, it's not tenable to offer the advice that those who do care about correctness should avoid Data.Convertible.Instances.Num.
That doesn't mean you can't add the LogFloat modules to base. Adding those modules shouldn't break what Haskell98 mandates, right?
The LogFloat module relies on Transfinite which in turn relies on PartialOrd. In particular the corrected versions of log, realToFrac, isNaN, and isInfinite are all used to ensure correctness of LogFloat. The RealToFrac class in Transfinite uses MPTCs which are non-Haskell98 and therefore restricts it to GHC or Hugs. Since base is used by nhc98 and other Haskell compilers, that means RealToFrac and LogFloat are not an option. In theory I could replace the type class with a polymorphic function using the default instance's definition, and use rewrite rules to replace that function with the efficient versions at particular types. In some ways this would be nicer (no overlapping instances here, though PartialOrd still uses them). But in other ways it's less nice since it just moves the problem elsewhere; namely, there are other Real types which may have additional values that cannot be expressed in Rational. The type class allows resolving this by testing for these values or placing additional constraints on the types, whereas the polymorphic function ---though more correct than the Prelude's--- would still be incorrect since the library cannot know about all such types. -- Live well, ~wren

wren ng thornton wrote:
[1] The Ord instance for Float and Double is also wrong, since NaN means there's no total ordering (and the existence of NaN is necessitated by the existence of Infinity). In addition to the fact that partial orderings are more common than total orderings, this means we should have a partial ordering class anyways.
Putting aside whether or not we should have a partial ordering class, you may be technically correct but practically quite wrong. To say that we ought not to be able to use <= on a Double or < on a Float is crazy. To say that I can't detect, with <= and the like, that 2.5 is between 2.0 and 5.0, just because I can't detect its relationship to NaN, strikes me as absurd. Let's not poison the well for 99% of uses because 1% of users have a leaky bucket. -- John

On Wed, Jan 28, 2009 at 5:28 PM, wren ng thornton
John Goerzen wrote:
wren ng thornton wrote: I wonder if you would consider submitting a patch to base? It seems that this is a sore problem there, and ideally should be dealt with properly in base.
As Bertram Felgenhauer says, it's not as easy as fixing base; the problem is an error in the Haskell98 specification. The Float and Double types contain exceptional values which cannot be represented in Rational (by definition). The only Haskell98 solution is to raise an error when attempting to convert those values into Rational, which isn't much of an improvement.
Is there a good reason why Rational is defined in a way that it can not represent Nan, Inf and -Inf? (Any other exceptional values I forgot?) Would fixing the definition so that it can represent those values be sufficient to fix this entire problem? Michael D. Adams mdmkolbe@gmail.com

On Wed, 2009-01-28 at 18:41 -0500, Michael D. Adams wrote:
On Wed, Jan 28, 2009 at 5:28 PM, wren ng thornton
wrote: John Goerzen wrote:
wren ng thornton wrote: I wonder if you would consider submitting a patch to base? It seems that this is a sore problem there, and ideally should be dealt with properly in base.
As Bertram Felgenhauer says, it's not as easy as fixing base; the problem is an error in the Haskell98 specification. The Float and Double types contain exceptional values which cannot be represented in Rational (by definition). The only Haskell98 solution is to raise an error when attempting to convert those values into Rational, which isn't much of an improvement.
Is there a good reason why Rational is defined in a way that it can not represent Nan, Inf and -Inf? (Any other exceptional values I forgot?) Would fixing the definition so that it can represent those values be sufficient to fix this entire problem?
It may well help conversions of floating point numbers through Rational but the contrary argument is that Rational is a well defined concept and that values like those do not belong in it. The problem seems to be that the various real/float classes in the Prelude cannot provide a class that allows conversions directly between Double and Float without going via a universal type because the class would either have to be multi-parameter or mention one of the types in the class definition. For integral types it's no problem to go via Integer but floating point types are a good deal more tricky. Duncan

Duncan Coutts wrote:
Michael D. Adams wrote:
Is there a good reason why Rational is defined in a way that it can not represent Nan, Inf and -Inf? (Any other exceptional values I forgot?) Would fixing the definition so that it can represent those values be sufficient to fix this entire problem?
It may well help conversions of floating point numbers through Rational but the contrary argument is that Rational is a well defined concept and that values like those do not belong in it.
The problem seems to be that the various real/float classes in the Prelude cannot provide a class that allows conversions directly between Double and Float without going via a universal type because the class would either have to be multi-parameter or mention one of the types in the class definition. For integral types it's no problem to go via Integer but floating point types are a good deal more tricky.
Exactly. Floating numbers also have other issues because of these values, e.g. they aren't Ord[1] and may not be Eq[2]. If we added the exceptional values to Rational we'd re-introduce all the problems they cause. As Duncan says, Rational is a well-defined concept by itself--- it's just a different concept than floating numbers. For what it's worth, GHC.Real does provide literals for 1%0 and 0%0. However, since these values cannot be constructed by the smart constructor (%), functions on Ratios assume they cannot exist and therefore treat them incorrectly or raise errors. [1] For those who think this is trivial, consider what value the `compare` function should return when one of the arguments is NaN. We don't even have a consistent lie for this! In Hugs it always returns EQ, whereas in GHC it always returns GT. Thus, not only is NaN not handled properly, but programs will behave differently depending on the compiler. [2] Since NaN are defined to be unequal to everything including themselves. This isn't damning per se, but it does go against the law of the excluded middle, which people typically assume of Eq. Whether Eq must obey this law or not is unspecified. (Not to mention the fuzziness issues.) -- Live well, ~wren

wren ng thornton wrote:
John Goerzen wrote:
Hi folks, I have uploaded a new package to Haskell: convertible. At its heart, it's a very simple typeclass that's designed to enable a reasonable default conversion between two different types without having to remember a bunch of functions.
I once again point out that realToFrac is *wrong* for converting from Float or Double.
Yes, realToFrac is just broken, or at least the Real instances of Float and Double are. Of course, with the restriction to single parameter type classes in Haskell98, it's hard to come up with anything better - we'd end up with fooToBar for all Real / Fractional pairs.
> realToFrac (1/0::Float) ::Double 3.402823669209385e38 > > realToFrac (0/0::Float) ::Double -5.104235503814077e38
> realToFrac (0/0::Double) ::Float -Infinity
[snip] GHC makes the mess just a bit messier. The following program prints different answers when compiled with -O or without: main = do print (realToFrac (1/0::Float) :: Double) print (realToFrac (0/0::Float) :: Double) print (realToFrac (0/0::Double) :: Float) Without -O: 3.402823669209385e38 -5.104235503814077e38 -Infinity With -O: Infinity NaN NaN The reason for this behaviour are rules replacing realToFrac by direct conversions from Float to Double or vice versa, where applicable. These two evils -- realToFrac and changing the behaviour with -O -- amount to something good: proper treatment of NaNs and Infinities. Your RealToFrac class is a definitive improvement over this situation. Bertram
participants (5)
-
Bertram Felgenhauer
-
Duncan Coutts
-
John Goerzen
-
Michael D. Adams
-
wren ng thornton