为什么 Haskell 难以解析 "overloaded" 运算符?

Why has Haskell troubles resolving "overloaded" operators?

This post 针对 !! 的情况提出问题。接受的答案告诉我们,您实际上在做的是创建一个新函数 !!,然后您应该避免导入标准函数。

但是,如果新函数要应用于与标准函数不同的类型,为什么要这样做呢?编译器不是可以根据参数选择合适的吗? 是否有任何编译器标志允许这样做?

例如,如果 * 未定义为 [Float] * Float

为什么编译器哭了

>  Ambiguous occurrence *
>  It could refer to either `Main.*', defined at Vec.hs:4:1
>                          or `Prelude.*',

对于此代码:

(*) :: [Float] -> Float -> [Float]
(*) as k = map (\a -> a*k) as  -- here: clearly Float*Float


r = [1.0, 2.0, 3.0] :: [Float]

s = r * 2.0 -- here: clearly [Float] * Float

main = do
     print r
     print s

允许编译器根据其类型选择正确的函数实现是类型类的目的。没有他们是不可能的。

为了证明这种方法的合理性,您可以阅读介绍它们的论文:How to make ad-hoc polymorphism less ad hoc [PDF]。

这是一个设计决定,不是理论问题,不把它包含在Haskell中。正如您所说,许多其他语言使用类型以特殊方式消除术语之间的歧义。但是类型 类 具有类似的功能,并且还允许对重载的事物进行抽象。类型导向的名称解析没有。

尽管如此,Haskell 已经讨论了类型导向的名称解析形式(例如在解析记录字段选择器的上下文中),并且某些类似于 Haskell 的语言支持这些形式,例如Agda(用于数据构造器)或 Idris(更普遍)。

真的,原因是这样的:在Haskell中,不一定有明确的关联“变量x有类型T.

Haskell 几乎和动态语言一样灵活,因为任何类型都可以是 类型变量 ,即可以具有多态类型。但是在动态语言(以及例如 OO 多态性或 C++ 模板)中,此类类型变量的类型基本上只是附加到代码中值变量的额外信息(因此重载运算符可以看到:参数是 Int->do this, is a String->do that), 在 Haskell 中,类型变量在 类型语言 中存在于完全独立的范围内.这给了你很多优势,例如,如果没有这样的系统,高种类的多态性几乎是不可能的。然而,这也意味着更难推断应该如何解决重载函数。如果 Haskell 允许您只编写重载并假设编译器会尽力猜测解决歧义,那么您通常会在意想不到的地方收到奇怪的错误消息。 (实际上,即使您有 no Hindley-Milner 类型系统,重载也很容易发生这种情况。C++ 因它而臭名昭著。)

相反,Haskell 选择强制显式重载。在重载方法之前,您必须先定义一个类型 class,虽然这不能完全排除令人困惑的编译错误,但可以更容易地避免它们。此外,它还允许您使用传统重载无法表达的类型解析来表达多态方法,特别是 多态结果(这非常适合编写非常容易重用的代码)。