Home > front end >  fromInteger is a cast?
fromInteger is a cast?

Time:11-04

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.

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 Integers, 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.

  • Related