为什么 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,虽然这不能完全排除令人困惑的编译错误,但可以更容易地避免它们。此外,它还允许您使用传统重载无法表达的类型解析来表达多态方法,特别是 多态结果(这非常适合编写非常容易重用的代码)。
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,虽然这不能完全排除令人困惑的编译错误,但可以更容易地避免它们。此外,它还允许您使用传统重载无法表达的类型解析来表达多态方法,特别是 多态结果(这非常适合编写非常容易重用的代码)。