The RealFloat
class is very old. Back when it was designed, nobody had worked out really good ways to pass extra type information to a function. At that time it was common to take an argument of the relevant type and expect the user to pass undefined
at that type. As leftaroundabout explained, GHC now has extensions that do this pretty nicely most of the time. But before TypeApplications
, two other techniques were invented to do this job more cleanly.
To disambiguate without GHC extensions, you can use either proxy passing or newtype-based tagging. I believe both techniques were given their final forms by Edward Kmett with a last polymorphic spin by Shachaf Ben-Kiki (see Who invented proxy passing and when?). Proxy passing tends to give an easy-to-use API, while the newtype approach can be more efficient under certain circumstances. Here's the proxy-passing approach. This requires you to pass an argument of some type. Traditionally, the caller will use Data.Proxy.Proxy
, which is defined
data Proxy a = Proxy
Here's how the class would look with proxy passing:
class (RealFrac a, Floating a) => RealFloat a where
floatRadix :: proxy a -> Integer
floatDigits :: proxy a -> Int
...
And here's how it would be used. Note that there's no need to pass in a value of the type you're talking about; you just pass the proxy constructor.
foo :: Int
foo = floatDigits (Proxy :: Proxy Double)
To avoid passing a runtime argument at all, you can use tagging. This is often done with the tagged
package, but it's quite easy to roll your own too. You could even reuse Control.Applicative.Const
, but that doesn't communicate the intention so well.
newtype Tagged t a = Tagged
{ unTagged :: a }
Here's how the class would look:
class (RealFrac a, Floating a) => RealFloat a where
floatRadix :: Tagged a Integer
floatDigits :: Tagged a Int
...
And here's how you'd use it:
foo :: Int
foo = unTagged (floatDigits :: Tagged Double Int)