为什么这个简单的函数计算平面中2个整数点之间的距离不能编译?
distance :: (Int, Int) -> (Int, Int) -> Double
distance (x, y) (u, v) = sqrt ((x - u) ^ 2 + (y - v) ^ 2)
我收到错误Couldn't match expected type ‘Double’ with actual type ‘Int’
。
令人沮丧的是,如此简单的数学函数耗费了我很多时间。任何解释为什么会出现这种问题并以最优雅的方式解决这个问题表示赞赏。
这是我解决问题的解决方案
distance :: (Int, Int) -> (Int, Int) -> Double
distance (x, y) (u, v) =
let xd = fromIntegral x :: Double
yd = fromIntegral y :: Double
ud = fromIntegral u :: Double
vd = fromIntegral v :: Double
in sqrt ((xd - ud) ^ 2 + (yd - vd) ^ 2)
但必须有一种更优雅的方式。
答案 0 :(得分:8)
大多数语言只在“数据流方向”进行类型推断(如果有的话)。例如,您从Java或Python中的值2
开始,它将是int
。您计算2 + 4
之类的内容,+
运算符从整数参数推断出结果也是int
。在动态语言中,这是可能的唯一方式(因为类型只是值的“关联属性”)。在像C ++这样的静态语言中,推理步骤只在编译时完成一次,但它仍然在很大程度上完成“好像类型是值的关联属性”。
在Haskell中并非如此。与其他Hindley-Milner languages一样,它有一个完全独立于任何运行时数据流方向的类型系统。它仍然可以进行前向推理((2::Int) + (4::Int)
明确地类型为Int
),但它只是一种特殊情况 - 类型也可以在“反向”推断,即如果你写{ {1}}编译器能够推断出(x + y) :: Int
和x
都必须具有y
类型。
这种反向多态性可以实现许多不错的技巧 - example:
Int
...但只有当语言永远不会进行隐式转换时才会起作用,即使在“安全†,明显的情况下”,例如Prelude Debug.SimpleReflect> 2 + 4 :: Expr
2 + 4
Prelude Debug.SimpleReflect> 7^3 :: Expr
7 * 7 * 7
。< / p>
通常,类型检查器会自动推断最合理的类型。对于您的原始实现,检查器将推断类型
Int -> Integer
那 - 或者也许是专门的版本
distance :: Floating a => (a, a) -> (a, a) -> a
是比distance :: (Double,Double) -> (Double,Double) -> Double
尝试更明智的类型,因为欧几里德距离在离散网格上实际上没有意义(你需要像Taxcab distance这样的东西。)
您实际想要的内容是来自(Int, Int) -> ...
包的distance
。这更通用,不仅适用于2元组,也适用于任何合适的空间。
† vector-space
实际上不是安全转换 - 在Python中尝试Int -> Double
!因此,即使没有Hindley-Milner,这也不是一个非常聪明的事情。
答案 1 :(得分:1)
已解决:现在我有了这个
distance :: (Int, Int) -> (Int, Int) -> Double
distance (x, y) (u, v) = sqrt (fromIntegral ((x - u) ^ 2 + (y - v) ^ 2))