假设我要在字符串上定义(+)
,而不是通过提供Num String
的实例。
为什么Haskell现在隐藏Num
的{{1}}函数?毕竟,我提供的功能是:
(+)
编译器可以通过Prelude的(+) :: String -> String -> String
来区分。为什么两个函数不能存在于同一名称空间中,而是具有不同的非重叠类型签名?
只要代码中没有调用函数,Haskell就会关心有一个ambiguitiy。然后使用参数调用函数将确定类型,以便可以选择适当的实现。
当然,一旦有一个实例(+)
,实际上就会发生冲突,因为在那时Haskell无法根据参数类型决定选择哪个实现,如果实际调用了该函数。
在这种情况下,应该提出错误。
这不会导致函数重载而没有陷阱/歧义吗?
注意:我不谈论动态绑定。
答案 0 :(得分:19)
Haskell根本不支持函数重载(除了通过类型类)。其中一个原因是函数重载不适用于类型推断。如果您有f x y = x + y
之类的代码,那么Haskell如何知道x
和y
是Nums还是字符串,即f
的类型应该是f :: Num a => a -> a -> a
还是f :: String -> String -> String
?
PS:这与你的问题并不真实相关,但是如果你假设一个开放的世界,那么这些类型并不是严格不重叠的,即在某个模块某处可能有Num String
的实例, ,导入时,会破坏您的代码。所以Haskell根本不会根据给定类型没有给定类型类的实例做出任何决定。当然,即使没有涉及类型类,函数定义也会隐藏其他具有相同名称的函数定义,正如我所说:与您的问题不相关。
为什么必须在定义站点知道函数的类型,而不是在调用站点推断:首先,函数的调用站点可能在与函数定义不同的模块中(或者在多个不同的模块中),因此如果我们必须查看调用站点以推断函数的类型,我们必须跨模块边界执行类型检查。那就是在对模块进行类型检查时,我们还必须完成导入该模块的所有模块,因此在最坏的情况下,每次更改单个模块时都必须重新编译所有模块。这将使编译过程变得非常复杂和变慢。更重要的是,这将使编译库变得不可能,因为库的本质是它们的函数将被编译器在编译库时无法访问的其他代码库使用。
答案 1 :(得分:7)
只要没有调用该函数
在某些时候, 时使用
功能
不不不。在Haskell中你不会想到“之前”或“你做的那一刻......”,而是一劳永逸地定义东西。这在变量的运行时行为中最为明显,但也转换为函数签名和类实例。这样,您不必对编译顺序进行所有繁琐的思考,并且从许多方面都是安全的,例如:由于程序中的一个微小变化,C ++模板/重载经常会崩溃。
另外,我认为你并不完全理解Hindley-Milner的作品。
在你调用函数之前,你知道参数的类型,它不需要知道。
好吧,你通常不知道参数的类型!它有时可以明确给出,但通常是从其他参数或返回类型推导出来的。例如,在
中map (+3) [5,6,7]
编译器不知道数字文字的类型,它只知道它们是数字。通过这种方式,您可以将结果评估为您喜欢的任何内容,并且允许您只能在其他语言中使用的内容,例如符号类型
> map (+3) [5,6,7] :: SymbolicNum
[SymbolicPlus 5 3, SymbolicPlus 6 3, SymbolicPlus 7 3]