Reputation: 15153
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
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
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
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