limp_chimp
limp_chimp

Reputation: 15153

Unintuitive type signature in Haskell

I made this (what I thought to be) fairly straightforward code to calculate the third side of a triangle:

toRadians :: Int -> Double
toRadians d = let deg = mod d 360
              in deg/180 * pi

lawOfCosines :: Int -> Int -> Int -> Double
lawOfCosines a b gamma = sqrt $ a*a + b*b - 2*a*b*(cos (toRadians gamma))

However, when I tried to load it into GHCi, I got the following errors:

[1 of 1] Compiling Main             ( law_of_cosines.hs, interpreted )

law_of_cosines.hs:3:18:
    Couldn't match expected type `Double' with actual type `Int'
    In the first argument of `(/)', namely `deg'
    In the first argument of `(*)', namely `deg / 180'
    In the expression: deg / 180 * pi

law_of_cosines.hs:6:26:
    No instance for (Floating Int)
      arising from a use of `sqrt'
    Possible fix: add an instance declaration for (Floating Int)
    In the expression: sqrt
    In the expression:
      sqrt $ a * a + b * b - 2 * a * b * (cos (toRadians gamma))
    In an equation for `lawOfCosines':
        lawOfCosines a b gamma
          = sqrt $ a * a + b * b - 2 * a * b * (cos (toRadians gamma))

law_of_cosines.hs:6:57:
    Couldn't match expected type `Int' with actual type `Double'
    In the return type of a call of `toRadians'
    In the first argument of `cos', namely `(toRadians gamma)'
    In the second argument of `(*)', namely `(cos (toRadians gamma))'

It turns out the fix was to remove my type signatures, upon which it worked fine.

toRadians d = let deg = mod d 360
              in deg/180 * pi

lawOfCosines a b gamma = sqrt $ a*a + b*b - 2*a*b*(cos (toRadians gamma))

And when I query the type of toRadians and lawOfCosines:

*Main> :t toRadians
toRadians :: (Floating a, Integral a) => a -> a
*Main> :t lawOfCosines
lawOfCosines :: (Floating a, Integral a) => a -> a -> a -> a
*Main>

Can someone explain to me what's going on here? Why the "intuitive" type signatures I had written were in fact incorrect?

Upvotes: 3

Views: 312

Answers (3)

yatima2975
yatima2975

Reputation: 6610

Seeing Floating a and Integral a in a type signature together always sets off my internal alarm bells, as these classes are supposed to be mutually exclusive - at least, there are no standard numeric types that are instances of both classes. GHCi tells me (along with a lot of other stuff):

> :info Integral
...
instance Integral Integer -- Defined in `GHC.Real'
instance Integral Int -- Defined in `GHC.Real'
> :info Floating
...
instance Floating Float -- Defined in `GHC.Float'
instance Floating Double -- Defined in `GHC.Float'

To see why these classes are mutually exclusive, let's have a look at some of the methods in both classes (this is going to be a bit handwavy). fromInteger in Integral converts an Integral number to an Integer, without loss of precision. In a way, Integral captures the essence of being (a subset of) the mathematical integers.

On the other hand, Floating contains methods such as pi and exp, which have a pronounced 'real number' flavour.

If there were a type that was both Floating and Integral, you could write toInteger pi and have a integer that was equal to 3.14159... - and that's not possible :-)


That said, you should change all your type signatures to use Double instead of Int; after all, not all triangles have integer sides, or angles that are an integral number of degrees!

If you absolutely don't want that for whatever reason, you also need to convert the sides (the a and b arguments) in lawOfCosines to Double. That's possible via

lawOfCosines aInt bInt gamma = sqrt $ a*a + b*b - 2*a*b*(cos (toRadians gamma)) where
    a = fromInteger aInt
    b = fromInteger bInt

Upvotes: 5

MathematicalOrchid
MathematicalOrchid

Reputation: 62818

The type signature for toRadians says it takes an Int but returns a Double. In some programming languages, the conversion from one to the other (but not back) happens automatically. Haskell is not such a language; you must manually request conversion, using fromIntegral.

The errors you are seeing are all coming from various operations which don't work on Int, or from trying to add Int to Double, or similar. (E.g., / doesn't work for Int, pi doesn't work for Int, sqrt doesn't work for Int...)

Upvotes: 4

amindfv
amindfv

Reputation: 8448

The problem is in toRadians: mod has the type Integral a => a -> a -> a, therefore, deg has the type Integral i => i (so either Int or Integer).

You then try and use / on deg, but / doesn't take integral numbers (divide integrals with div):

(/) :: Fractional a => a -> a -> a

The solution is to simply use fromIntegral :: (Integral a, Num b) => a -> b:

toRadians :: Int -> Double
toRadians d = let deg = mod d 360
              in (fromIntegral deg)/180 * pi

Upvotes: 11

Related Questions