简体   繁体   English

Haskell中的非直观类型签名

[英]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: 但是,当我尝试将其加载到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))'

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 : 当我查询toRadianslawOfCosines的类型时:

*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? 为什么我写的“直观”类型签名实际上是不正确的?

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 ). 问题出在toRadiansmod的类型为Integral a => a -> a -> a ,因此, deg的类型为Integral i => i (因此IntInteger )。

You then try and use / on deg , but / doesn't take integral numbers (divide integrals with div ): 然后你尝试使用/ on deg ,但是/不带整数(用div除以积分):

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

The solution is to simply use fromIntegral :: (Integral a, Num b) => a -> b : 解决方案是简单地使用fromIntegral :: (Integral a, Num b) => a -> b

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

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. 在一个类型签名中看到Floating aIntegral a总是引发我的内部警报,因为这些类应该是互斥的 - 至少,没有标准数字类型是两个类的实例。 GHCi tells me (along with a lot of other stuff): 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'

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. fromInteger in IntegralIntegral数转换为Integer ,而不会损失精度。 In a way, Integral captures the essence of being (a subset of) the mathematical integers. 在某种程度上, Integral捕获了数学整数的存在(的一个子集)的本质。

On the other hand, Floating contains methods such as pi and exp , which have a pronounced 'real number' flavour. 另一方面, Floating包含诸如piexp ,它们具有明显的“实数”风格。

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 :-) 如果有一个同时属于FloatingIntegral的类型,你可以编写toInteger pi并且有一个等于3.14159的整数...... - 这是不可能的:-)


That said, you should change all your type signatures to use Double instead of Int ; 也就是说,您应该更改所有类型的签名以使用Double而不是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 . 如果你因为某种原因绝对不希望这样,你还需要将lawOfCosines中的lawOfCosinesab参数) lawOfCosinesDouble 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

The type signature for toRadians says it takes an Int but returns a Double . toRadians的类型签名表示它需要一个Int但返回一个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 . 您必须使用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. 您看到的错误都来自于不适用于Int各种操作,或者尝试将Int添加到Double或类似的操作。 (Eg, / doesn't work for Int , pi doesn't work for Int , sqrt doesn't work for Int ...) (例如, /不适用于Intpi不适用于Intsqrt不适用于Int ......)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM