考虑一下这个功能
add a b = a + b
这有效:
*Main> add 1 2
3
但是,如果我添加一个类型签名,指定我想添加相同类型的东西:
add :: a -> a -> a
add a b = a + b
我收到一个错误:
test.hs:3:10:
Could not deduce (Num a) from the context ()
arising from a use of `+' at test.hs:3:10-14
Possible fix:
add (Num a) to the context of the type signature for `add'
In the expression: a + b
In the definition of `add': add a b = a + b
所以GHC显然可以推断我需要 Num
类型约束,因为它只是告诉我:
add :: Num a => a -> a -> a
add a b = a + b
作品 .
为什么GHC要求我添加类型约束?如果我'm doing generic programming, why can' t它适用于任何知道如何使用 +
运算符的东西?
在C模板编程中,您可以轻松完成此操作:
#include <string>
#include <cstdio>
using namespace std;
template<typename T>
T add(T a, T b) { return a + b; }
int main()
{
printf("%d, %f, %s\n",
add(1, 2),
add(1.0, 3.4),
add(string("foo"), string("bar")).c_str());
return 0;
}
编译器计算 add
的参数类型,并为该类型生成函数的版本 . Haskell的方法似乎存在根本区别,您能描述一下,并讨论权衡吗?在我看来,如果GHC只是为我填写了类型约束,它会得到解决,因为它显然决定了它是必要的 . 仍然,为什么类型约束呢?为什么不只是成功编译,只要该函数仅用于参数在 Num
的有效上下文中?
4 回答
如果您没有't want to specify the function'类型,只需将其保留,编译器将自动推断类型 . 但是,如果您选择指定类型,则必须正确且准确 .
类型的全部意义在于有一种正式的方式来声明使用函数的正确和错误方式 . 一种
(Num a) => a -> a -> a
描述了参数所需的确切内容 . 如果省略了类约束,那么您将拥有一个更通用的函数,可以在更多地方使用(错误地) .并且它不仅阻止您将非
Num
值传递给add
. 功能到处都是,类型肯定会去 . 考虑一下:你希望编译器拒绝这个,对吧?它是如何做到的?通过添加类约束,并检查它们是否未被删除,即使您没有直接命名该函数 .
C版本有什么不同?没有类约束 . 编译器将
int
或std::string
替换为T
,然后尝试编译生成的代码并查找可以使用的匹配+
运算符 . 模板系统“更宽松”,因为它接受更多无效程序,这是在编译之前它是一个单独阶段的症状 . 我想修改C以从Java的泛型中添加<? extends T>
语义 . 只需学习类型系统并认识到参数多态性比C模板“更强”,即它将拒绝更多无效程序 .我认为你可能会被GHC "crazy moon poetry"绊倒,并没有说它(GHC)无法推断
(Num a)
约束 . 它是说(Num a)
约束不能从你的类型签名推断出来,它知道必须在+
的使用中 . 因此,你希望你向世界撒谎你的功能!在第一个示例中,如果在ghci中运行
:t add
,则在没有类型签名的情况下,您将看到编译器完全知道(Num a)
约束存在 .至于C的模板,请记住它们是语法模板,并且在使用它们时仅在每个实例中进行完全类型检查 . 您的
add
模板将适用于任何类型,只要在每个使用它的地方都有合适的+
运算符和可能的转换,以使模板的实例可行 . 在此之前不能保证模板...这就是为什么模板的主体必须是"visible"到使用它的每个模块 .基本上,所有C可以做的是验证模板的语法,然后将其作为一种非常卫生的宏保存 . 而Haskell为
add
生成一个真正的函数(除了它可以选择也生成特定类型的特化以进行优化) .在某些情况下,编译器 can't 会为您找出合适的类型以及需要您帮助的位置 . 考虑
编译器说:
(奇怪的是,似乎你可以在ghci中定义这个函数,但似乎没有办法实际使用它)
如果你想要像
f "1"
这样的东西,你 need 指定类型: