3

我做了这个(我认为是)相当简单的代码来计算三角形的第三边:

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))

但是,当我尝试将其加载到 GHCi 中时,出现以下错误:

[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))'

事实证明,修复是删除我的类型签名,它工作正常。

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 的类型toRadianslawOfCosines

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

有人可以向我解释这里发生了什么吗?为什么我写的“直观”类型签名实际上是不正确的?

4

3 回答 3

11

问题出在toRadians:mod有类型Integral a => a -> a -> a,因此deg有类型Integral i => i(所以要么Intor Integer)。

然后您尝试使用/on deg,但/不采用整数(将积分除以div):

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

解决方案是简单地使用fromIntegral :: (Integral a, Num b) => a -> b

toRadians :: Int -> Double
toRadians d = let deg = mod d 360
              in (fromIntegral deg)/180 * pi
于 2013-03-21T19:13:39.663 回答
5

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
于 2013-03-21T20:28:48.327 回答
4

的类型签名toRadians说它需要一个Int但返回一个Double。在某些编程语言中,从一种转换到另一种(但不会返回)是自动发生的。Haskell 不是这样的语言。您必须手动请求转换,使用fromIntegral.

您看到的错误都来自各种不起作用的操作Int,或者来自尝试添加IntDouble或类似的操作。(例如,/不适用于Intpi不适用于Intsqrt不适用于Int...)

于 2013-03-21T20:16:25.460 回答