Haskell中的非直观类型签名

时间:2013-03-21 19:07:51

标签: haskell type-signature

我做了这个(我认为是)相当直接的代码来计算三角形的第三面:

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

当我查询toRadianslawOfCosines的类型时:

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

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

3 个答案:

答案 0 :(得分:11)

问题出在toRadiansmod的类型为Integral a => a -> a -> a,因此,deg的类型为Integral i => i(因此IntInteger)。

然后,您尝试在/上使用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

答案 1 :(得分:5)

在一起看到类型签名中的Floating aIntegral a总会引发我的内部警报,因为这些类应该是互斥的 - 至少,没有标准的数字类型是实例两个班级。 GHCi告诉我(还有很多其他的东西):

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

要了解为什么这些类是互斥的,让我们看看这两个类中的一些方法(这有点像手工一样)。 fromInteger中的Integral会将Integral个数字转换为Integer,而不会失去精确度。在某种程度上,Integral捕获了数学整数的存在(的一个子集)的本质。

另一方面,Floating包含piexp等方法,这些方法具有明显的“实数”风格。

如果某个类型同时包含FloatingIntegral,则可以编写toInteger pi并使用等于3.14159的整数... - 这是不可能的: - )


也就是说,您应该更改所有类型的签名以使用Double而不是Int;毕竟,并非所有三角形都有整数边,或者是整数度数的角度!

如果绝对因任何原因不想要这样做,您还需要在a中转换边(blawOfCosines参数)到Double。这可以通过

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

答案 2 :(得分:4)

toRadians的类型签名表示它需要Int,但会返回Double。在某些编程语言中,从一个到另一个(但不是后退)的转换会自动发生。哈斯克尔不是这样的语言;您必须手动请求转换,使用fromIntegral

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