Reputation: 438
As far as I can tell, GHC can convert any numeric literal with default polymorphic type Num a => a
into any type with an instance of Num
. I'd like to know whether this is true and a little about the underlying mechanism.
To explore this, I have written a datatype called MySum
which replicates (part of) the functionality of Sum
from Data.Monoid. The most important part is that it contains instance Num a => Num (MySum a)
.
Note - This just so happens to be where my question go its start. The Monoid is not specifically relevant. I've included a portion of that code at the bottom of this question, just in case it is useful for an answer to refer to the contents.
It seems that GHCi will happily comply with an input of the form "v :: MySum t", under the following conditions:
v is a polymorphic value of type Num a => a
t is a (possibly polymorphic) type under Num
As far as I can tell, the only numeric literals compatible with the type Num a => a
are ones that look like integers. Is this always the case? It seems to imply that a value can instantiated to any type under Num exactly when that value is integral. If this is correct, then I understand how something like 5 :: MySum Int
might work, given the function fromInteger
in Num
.
With all of that said, I can't figure out how something like this works:
*Main Data.Monoid> 5 :: Fractional a => MySum a
MySum {getMySum = 5.0}
If it's possible to explain this in a novice-friendly way, I would appreciate it.
The instance Num a => Num (MySum a)
, as promised:
import Control.Applicative
newtype MySum a = MySum {getMySum :: a}
deriving Show
instance Functor MySum where
fmap f (MySum a) = MySum (f a)
instance Applicative MySum where
pure = MySum
(MySum f) <*> (MySum a) = MySum (f a)
instance Num a => Num (MySum a) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = fmap negate
abs = fmap abs
signum = fmap signum
fromInteger = pure . fromInteger
Upvotes: 2
Views: 753
Reputation: 1526
You mostly have it correct: an integer literal 5
is equivalent to fromInteger (5 :: Integer)
, and thus has type Num a => a
; and a floating-point literal 5.0
is equivalent to fromRational (5.0 :: Rational)
and has type Fractional a => a
. This does indeed explain 5 :: MySum Int
. 5 :: Fractional a => MySum a
isn't that much trickier. Per the above rule, this expands to:
fromInteger (5 :: Integer) :: Fractional a => MySum a
fromInteger
has type Num b => Integer -> b
. So for the above expression to type check, GHC has to unify b
with MySum a
. So now GHC has to solve Num (MySum a)
given Fractional a
. Num (MySum a)
is solved by your instance, producing the constraint Num a
. Num
is a superclass of Fractional
, so any solution to Fractional a
will also be a solution to Num a
. So everything checks out.
You might be wondering though, if 5
gets passed through fromInteger
here, why does the value that ends up inside MySum
look like a Double
in GHCi? This is because, after type checking, Fractional a => MySum a
is still ambiguous—when GHCi goes to print that value, it needs to actually choose an a
in order to select an appropriate Fractional
instance, after all. If we weren't dealing with numbers, we might end up with GHC complaining about this ambiguity in a
.
But there's a special case in the Haskell standard for this. The brief overview is, if you have an ambiguity issue like the above that involves only numeric type classes, Haskell in its wisdom will pick either Integer
or Double
for the ambiguous type and run with the first one that type checks. In this case, that's Double
. If you'd like to know more about this feature, this blog post does a decent job of motivating and elaborating on what the standard says.
Upvotes: 3
Reputation: 34378
As you have found out, the integer literal 5
amounts to:
fromInteger 5
As the type of fromInteger
is Num a => Integer -> a
, you can instantiate 5
to the Num
instance of your choice, be it Int
, Double
, MySum Double
, or anything else. In particular, given that Fractional
is a subclass of Num
, and that you wrote a Num a => Num (MySum a)
instance, 5 :: Fractional a => MySum a
works just fine:
5 :: Fractional a => MySum a
fromInteger 5 :: Fractional a => MySum a
(pure . fromInteger) 5 :: Fractional a => MySum a
MySum (fromInteger 5 :: Fractional a => a)
It seems to imply that a value can instantiated to any type under Num exactly when that value is integral.
Things get a little subtle here. An integral value can be converted to any type under Num
(via fromInteger
and, in the general case, fromIntegral
). We can instantiate an integer literal like 5
as anything under Num
because GHC handles the conversion for us, by desugaring it to fromInteger 5 :: Num a => a
. However, we can't instantiate the monomorphic value 5 :: Integer
as a Double
, nor can we instantiate 5 :: Integral a => a
to a non-Integral
type like Double
. In those two cases, the type annotations further restrict the type, so that we have to perform the conversion explicitly if we want a Double
.
Upvotes: 4