I'm looking at this, as well as contemplating the whole issue of non-decimal literals, e.g., 1
, being just sugar for fromInteger 1
and then I find the type is
λ> :t 1
1 :: Num p => p
This and the statement
An integer literal represents the application of the function fromInteger to the appropriate value of type Integer.
have me wondering what is really going on. Likewise,
λ> :t 3.149
3.149 :: Fractional p => p
Richard Bird says
A floating-point literal such as
3.149
represents the application offromRational
to an appropriate rational number. Thus3.149 :: Fractional a => a
Not understanding what the application of fromRational
to an appropriate rational number means. Then he says this is all necessary to be able to add, e.g., 42 3.149
.
I feel there's a lot going on here that I just don't understand. Like there's too much hand-waving for me. It seems like a cast of an unidentified non-decimal or decimal to specific types, Integer
and Rational
. So first, why is 1
actually fromInteger 1
internally? I realize every expression must be evaluated as a type, but why is fromInteger
and fromRational
involved?
Auxillary
So at this page
The workhorse for converting from integral types is
fromIntegral
, which will convert from anyIntegral
type into any Numeric type (which includesInt
,Integer
,Rational
, andDouble
):fromIntegral :: (Num b, Integral a) => a -> b
Then comes the example
λ> sqrt 1
1.0
λ> sqrt (1 :: Int)
... error...
λ> sqrt (fromInteger 1)
1.0
λ> :t sqrt 1
sqrt 1 :: Floating a => a
λ> :t sqrt (1 :: Int)
...error...
λ> :t sqrt
sqrt :: Floating a => a -> a
λ> :t sqrt (fromInteger 1)
sqrt (fromInteger 1) :: Floating a => a
So yes, this is a cast, but I don't know the mechanism of how fromI*
is doing this --- since technically it's not a cast in a C/C sense. All instances of Num
must have a fromInteger
. It seems like under the hood Haskell is taking whatever you put in and generic-izing it to Integer
or Rational
, then "giving it back" to the original function, e.g., with sqrt (fromInteger 1)
being of type Floating a => a
. This is very mysterious to someone prone to over-thinking.
So yes, 1
is a literal, a constant that is polymorphic. It may represent 1
in any type that instantiates Num
. The role of fromInteger
must be to allowing a value (a cast) to be extracted from an integer constant consistent with what the situation calls for. But this is hand-waving talk at some point. I dont' get how this is actually happening.
CodePudding user response:
Perhaps this will help...
Imagine a language, like Haskell, except that the literal program text 1
represents a term of type Integer
with value one, and the literal program text 3.14
represents a term of type Rational
with value 3.14. Let's call this language "AnnoyingHaskell".
To be clear, when I say "represents" in the above paragraph, I mean that the AnnoyingHaskell compiler actually compiles those literals into machine code that produces an Integer
term whose value is the number 1 in the first case, and a Rational
term whose value is the number 3.14 in the second case. An Integer
is -- at it's core -- an arbitrary precision integer as implemented by the GMP library, while a Rational
is a pair of two Integer
s, understood to be the numerator and denominator of a rational number. For this particular rational, the two integers would be 157 and 50 (i.e., 157/50=3.14).
AnnoyingHaskell would be... erm... annoying to use. For example, the following expression would not type check:
take 3 "hello"
because 3
is an Integer
but take
's first argument is an Int
. Similarly, the expression:
42 3.149
would not type check, because 42
is an Integer
and 3.149
is a Rational
, and in AnnoyingHaskell, as in Haskell itself, you cannot add an Integer
and a Rational
.
Because this is annoying, the designers of Haskell made the decision that the literal program text 42
and 3.149
should be treated as if they were the AnnoyingHaskell expressions fromInteger 42
and fromRational 3.149
.
The AnnoyingHaskell expression:
fromInteger 42 fromRational 3.149
does type check. Specifically, the polymorphic function:
fromInteger :: (Num a) => Integer -> a
accepts the AnnoyingHaskell literal 42 :: Integer
as its argument, and the resulting subexpression fromInteger 42
has resulting type Num a => a
for some fresh type a
. Similarly, fromRational 3.149
is of type Fractional b => b
for some fresh type b
. The
operator unifies these two types into a single type (Num c, Fractional c) => c
, but Num c
is redundant because Num
is a superclass of Fractional
, so the whole expression has a polymorphic type:
fromInteger 42 fromRational 3.149 :: Fractional c => c
That is, this expression can be instantiated at any type with a Fractional
constraint. For example. In the Haskell program:
main = print $ 42 3.149
which is equivalent to the AnnoyingHaskell program:
main = print $ fromInteger 42 fromRational 3.149
the usual "defaulting" rules apply, and because the expression passed to the print
statement is an unknown type c
with a Fractional c
constraint, it is defaulted to Double
, allowing the program to actually run, computing and printing the desired Double
.
If the compiler was awful, this program would run by creating a 42 :: Integer
on the heap, calling fromInteger
(specialized to fromInteger :: Integer -> Double
) to create a 42 :: Double
, then create 3.149 :: Rational
on the heap, calling fromRational
(specialized to fromRational :: Rational -> Double
) to create a 3.149 :: Double
, and then add them together to create the final answer 45.149 :: Double
. Because the compiler isn't so awful, it just creates the number 45.149 :: Double
directly.
CodePudding user response:
Perhaps this will help more. One thing you seem to be struggling with is the nature of a value of type Num a => a
, like the one produced by fromInteger (1 :: Integer)
. I think you're somehow imagining that fromInteger
"packages" up the 1 :: Integer
in a box so it can later be cast by special compiler magic to a 1 :: Int
or 1 :: Double
.
That's not what's happening.
Consider the following type class:
class Thing a where
thing :: a
with associated instances:
instance Thing Bool where thing = True
instance Thing Int where thing = 16
instance Thing String where thing = "hello, world"
instance Thing (Int -> String) where thing n = replicate n '*'
and observe the result of running:
main = do
print (thing :: Bool)
print (thing :: Int)
print (thing :: String)
print $ (thing :: Int -> String) 15
Hopefully, you're comfortable enough with type classes that you don't find the output surprising. And presumably you don't think that thing
contains some specific, identifiable "thing" that is being "cast" to a Bool
, Int
, etc. It's simply that thing
is a polymorphic value whose definition depends on its type; that's just how type classes work.
Now, consider the similar example:
import Data.Ratio
import GHC.Float (castWord64ToDouble)
class Three a where
three :: a
instance Three Int where
three = length "aaa"
instance Three Double where
three = castWord64ToDouble 0x4008000000000000
instance Three Rational where
three = (6 :: Integer) % (2 :: Integer)
main = do
print (three :: Int)
print (three :: Double)
print (three :: Rational)
print $ take three "abcdef"
print $ sqrt three -- defaults to Double
Can you see here how three :: Three a => a
represents a value that can be used as an Int
, Double
, or Rational
? If you want to think of it as a cast, that's fine, but obviously there's no identifiable single "3" that's packaged up in the value three
being cast to different types by compiler magic. It's just that a different definition of three
is invoked, depending on the type demanded by the caller.
From here, it's not a big leap to:
{-# LANGUAGE MagicHash #-}
import Data.Ratio
import GHC.Num
import GHC.Int
import GHC.Float (castWord64ToDouble)
class MyFromInteger a where
myFromInteger :: Integer -> a
instance MyFromInteger Integer where
myFromInteger x = x
instance MyFromInteger Int where
-- Note: data Integer = IS Int# | ...
myFromInteger (IS i) = I# i
myFromInteger _ = error "not supported"
instance MyFromInteger Rational where
myFromInteger x = x % (1 :: Integer)
main = do
print (myFromInteger 1 :: Integer)
print (myFromInteger 2 :: Int)
print (myFromInteger 3 :: Rational)
print $ take (myFromInteger 4) "abcdef"
Conceptually, the base library's fromInteger (1 :: Integer) :: Num a => a
is no different than this code's myFromInteger (1 :: Integer) :: MyFromInteger a => a
, except that the implementations are better and more types have instances.
See, it's not that the expression fromInteger (1 :: Integer)
boxes up a 1 :: Integer
into a package of type Num a => a
for later casting. It's that the type context for this expression causes dispatch to an appropriate Num
type class instance, and a different definition of fromInteger
is invoked, depending on the required type. That fromInteger
function is always called with argument 1 :: Integer
, but the returned type depends on the context, and the code invoked by the fromInteger
call (i.e., the definition of fromInteger
used) to convert or "cast" the argument 1 :: Integer
to a "one" value of the desired type depends on which return type is demanded.
And, to go a step further, as long as we take care of a technical detail by turning off the monomorphism restriction, we can write:
{-# LANGUAGE NoMonomorphismRestriction #-}
main = do
let two = myFromInteger 2
print (two :: Integer)
print (two :: Int)
print (two :: Rational)
This may look strange, but just as myFromInteger 2
is an expression of type Num a => a
whose final value is produced using a definition of myFromInteger
, depending on what type is ultimately demanded, the expression two
is also an expression of type Num a => a
whose final value is produced using a definition of myFromInteger
that depends on what type is ultimately demanded, even though the literal program text myFromInteger
does not appear in the expression two
. Moreover, continuing with:
let four = two two
print (four :: Integer)
print (four :: Int)
print (four :: Rational)
the expression four
of type Num a => a
will produce a final value that depends on the definition of myFromInteger
and the definition of ( )
that are determined by the finally demanded return type.
In other words, rather than thinking of four
as a packaged 4 :: Integer
that's going to be cast to various types, you need to think of four
as completely equivalent to its full definition:
four = myFromInteger 2 myFromInteger 2
with a final value that will be determined by using the definitions of myFromInteger
and ( )
that are appropriate for whatever type is demanded of four
, whether its four :: Integer
or four :: Rational
.
The same goes for sqrt (fromIntegral 1)
After:
x = sqrt (fromIntegral (1 :: Integer))
the value of x :: Floating a => a
is equivalent to the full expression:
sqrt (fromIntegral (1 :: Integer))
and every place it is is used, it will be calculated using definitions of sqrt
and fromIntegral
determined by the Floating
and Num
instances for the final type demanded.