Search code examples
haskelltypesnumbers

fromInteger is a cast?


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 of fromRational to an appropriate rational number. Thus 3.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 any Integral type into any Numeric type (which includes Int, Integer, Rational, and Double): 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.


Solution

  • 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:

    {-# LANGUAGE FlexibleInstances #-}
    
    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:

    {-# LANGUAGE FlexibleInstances #-}
    
    import Data.Ratio
    import Data.Word
    import Unsafe.Coerce
    
    class Three a where
      three :: a
    
    -- for base >= 4.10.0.0, can import GHC.Float (castWord64ToDouble)
    -- more generally, we can use this unsafe coercion:
    castWord64ToDouble :: Word64 -> Double
    castWord64ToDouble w = unsafeCoerce w
    
    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 :: 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 FlexibleInstances #-}
    {-# LANGUAGE MagicHash #-}
    
    import Data.Ratio
    import Data.Word
    import Unsafe.Coerce
    
    class MyFromInteger a where
      myFromInteger :: Integer -> a
    
    instance MyFromInteger Integer where
      myFromInteger x = x
    instance MyFromInteger Int where
      -- for base >= 4.10.0.0 can use the following:
      -- -- Note: data Integer = IS Int | ...
      -- myFromInteger (IS i) = I# i
      -- myFromInteger _ = error "not supported"
      -- to support more GHC versions, we'll just use this extremely
      -- dangerous coercion:
      myFromInteger i = unsafeCoerce i
    
    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.

    Here's all the code in one file, testing with GHC 8.2.2 and 9.2.4.

    {-# LANGUAGE Haskell98 #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE MagicHash #-}
    {-# LANGUAGE NoMonomorphismRestriction #-}
    
    import Data.Ratio
    import GHC.Num
    import GHC.Int
    import GHC.Float (castWord64ToDouble)
    
    class Thing a where
      thing :: a
    
    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 '*'
    
    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)
    
    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 (thing :: Bool)
      print (thing :: Int)
      print (thing :: String)
      print $ (thing :: Int -> String) 15
    
      print (three :: Int)
      print (three :: Double)
      print (three :: Rational)
      print $ take three "abcdef"
      print $ (sqrt three :: Double)
    
      print (myFromInteger 1 :: Integer)
      print (myFromInteger 2 :: Int)
      print (myFromInteger 3 :: Rational)
      print $ take (myFromInteger 4) "abcdef"
    
      let two = myFromInteger 2
      print (two :: Integer)
      print (two :: Int)
      print (two :: Rational)
    
      let four = two + two
      print (four :: Integer)
      print (four :: Int)
      print (four :: Rational)