在haskell中,为什么我需要指定类型约束,为什么编译器不能解决它们?

Ste*_*eve 6 haskell type-systems type-inference typeclass

考虑一下这个功能

add a b = a + b
Run Code Online (Sandbox Code Playgroud)

这有效:

*Main> add 1 2
3
Run Code Online (Sandbox Code Playgroud)

但是,如果我添加一个类型签名,指定我想添加相同类型的东西:

add :: a -> a -> a
add a b = a + b
Run Code Online (Sandbox Code Playgroud)

我收到一个错误:

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
Run Code Online (Sandbox Code Playgroud)

所以GHC显然可以推断我需要Num类型约束,因为它只是告诉我:

add :: Num a => a -> a -> a
add a b = a + b
Run Code Online (Sandbox Code Playgroud)

作品.

为什么GHC要求我添加类型约束?如果我正在进行通用编程,为什么它不能只适用于任何知道如何使用+运算符的东西?

在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;
}
Run Code Online (Sandbox Code Playgroud)

编译器计算出参数的类型,并为该类型add生成函数的版本.Haskell的方法似乎存在根本区别,您能描述一下,并讨论权衡吗?在我看来,如果GHC只是为我填写了类型约束,它会得到解决,因为它显然决定了它是必要的.仍然,为什么类型约束呢?为什么不只是成功编译,只要该函数仅用于参数所在的有效上下文中Num

Tal*_*man 15

如果您不想指定函数的类型,只需将其保留,编译器将自动推断类型.但是,如果您选择指定类型,则必须正确且准确.

  • 我觉得你在这里打了一针.如果要指定类型,那么您不希望编译器忽略您的规范(或"更正"它) (3认同)

Jos*_*Lee 15

类型的全部意义在于有一种正式的方式来声明使用函数的正确和错误方式.一种(Num a) => a -> a -> a描述参数所需的确切内容.如果省略了类约束,那么您将拥有一个更通用的函数,可以在更多地方使用(错误地).

而且它不仅阻止你传递非Numadd.功能到处都是,类型肯定会去.考虑一下:

add :: a -> a -> a
add a b = a + b
foo :: [a -> a -> a]
foo = [add]
value :: [String]
value = [f "hello" "world" | f <- foo]
Run Code Online (Sandbox Code Playgroud)

你希望编译器拒绝这个,对吧?它是如何做到的?通过添加类约束,并检查它们是否未被删除,即使您没有直接命名该函数.

C++版本有什么不同?没有类约束.编译器的替代品intstd::stringT,然后尝试编译生成的代码并寻找匹配的+,它可以使用运营商.模板系统"更宽松",因为它接受更多无效程序,这是在编译之前它是一个单独阶段的症状.我会喜欢修改C++添加<? extends T>从Java的泛型语义.只需学习类型系统并认识到参数多态性比C++模板"更强大",即它将拒绝更多无效程序.

  • @adamax:是的,这在技术上是可行的,但是我如何向编译器断言函数具有无约束类型`a - > a - > a`?这是所有对自己完全有意义的类型,如果我告诉我的功能有这样一种类型的编译器,我真的不希望它只是默默地对自己说,"OK,他说:'A - > A - >一’ ,但我知道他实际上是指Foo a => a - > a - > a".如果它对自己说,我希望它告诉我我错了,因为我_was_. (18认同)

Mtn*_*ark 12

我想你可能会被GHC的错误信息的"疯狂月亮诗"绊倒.这不是说,(即GHC)不能推导出(Num a)约束.这就是说,(Num a)约束不能从你的类型签名中推断出来,它知道必须在使用时使用+.因此,您正在声明此函数的类型比编译器知道的更通用.编译器不希望你对世界的功能撒谎!

在第一个示例中,如果您:t add在ghci中运行,则在没有类型签名的情况下,您将看到编译器完全知道(Num a)约束存在.

至于C++的模板,请记住它们是语法模板,并且只在使用它们时在每个实例中进行完全类型检查.您的add模板将适用于任何类型,只要在每个使用它的地方,都有合适的+运算符和可能的转换,以使模板的实例可行.在此之前不能保证模板...这就是为什么模板的主体必须对使用它的每个模块"可见".

基本上,所有C++都可以做的是验证模板的语法,然后将其作为一种非常卫生的宏保存.而Haskell生成一个真正的函数add(除了它可以选择也生成特定类型的特化以进行优化).