19

根据我对 Haskell 的了解以及我对 GHC 所做的实验,Haskell 似乎具有返回类型重载(也称为临时多态性)。这方面的一个例子是一个fromInteger函数,它可以根据结果的使用位置为您提供 aDouble或 an 。Integer例如:

fd :: Double -> String
fd x = "Double"

fi :: Integer -> String
fi x = "Integer"

fd (fromInteger 5)  -- returns "Double"
fi (fromInteger 5)  -- returns "Integer"

Haskell 的温和介绍似乎同意这一点,它说:

到目前为止我们所讨论的这种多态性通常称为参数多态性。还有另一种称为 ad hoc 多态性,更广为人知的是重载。以下是一些临时多态性的示例:

  • 文字 1、2 等通常用于表示固定精度和任意精度的整数。

如果数字文字被认为是临时多态性(又名重载)的一个例子,那么对于像fromInteger.

事实上,我在 Stack Overflow 上找到了一些其他问题的答案,这些问题表明 Haskell 具有按返回类型重载的功能。

但是,至少有一位 Haskell 程序员告诉我,这不是返回类型重载,而是“参数多态性,其中参数受通用量词约束”的示例。

认为他的意思是从(某种非确定性类型)的fromInteger每个实例返回一个值。Num

这似乎是一个合理的解释,但据我所知,Haskell 永远不会让我们查看这些实例值中的一个以上(部分归功于Monomorphism 限制)。似乎我们看到的实际实例的价值也可以静态确定。由于所有这些,似乎可以合理地说表达式fd (fromInteger 5)中的子表达式fromInteger 5是 type Double,而表达式fi (fromInteger 5)中的子表达式fromInteger 5是 type Integer

那么,Haskell 有返回类型重载吗?

如果不是,请提供以下其中一项的示例:

  • 如果 Haskell 具有返回类型重载,则有效的 Haskell 代码将具有不同的行为
  • 如果 Haskell 具有返回类型重载,则有效的 Haskell 代码将无效
  • 如果 Haskell 具有返回类型重载,则无效的 Haskell 代码将有效
4

5 回答 5

17

好吧,一种看待它的方法是,Haskell 将您正在考虑的返回类型多态性转换为参数多态性,使用称为类型类的字典传递翻译的东西。(虽然这不是实现类型类或推理它们的唯一方法;它只是最流行的。)

基本上,fromInteger在 Haskell 中有这种类型:

fromInteger :: Num a => Integer -> a

这可能会在内部翻译成如下内容:

fromInteger# :: NumDictionary# a -> Integer -> a
fromInteger# NumDictionary# { fromInteger = method } x = method x

data NumDictionary# a = NumDictionary# { ...
                                       , fromInteger :: Integer -> a
                                       , ... }

因此,对于每个T具有Num实例的具体类型,都有一个NumDictionary# T包含 function 的值fromInteger :: Integer -> T,并且所有使用该Num类型类的代码都被转换为以字典作为参数的代码。

于 2012-07-10T19:14:26.140 回答
13

关于 Haskell 风格类型类的开创性论文被称为“如何使 ad-hoc polymorphism less ad hoc”。因此,您的问题的答案是合格的“是”——这取决于您要求返回类型重载的临时性...

换句话说:毫无疑问,即席多态性与类型类相关,因为这是发明它们的一个激励例子。但是您是否认为结果仍然符合“返回类型重载”的条件,取决于您喜欢的定义的繁琐细节。

于 2012-07-10T19:23:14.640 回答
12

我想解决你问题的一小部分:

似乎我们看到的实际实例的价值也可以静态确定。

这并不准确。考虑以下古怪的数据类型:

data PerfectlyBalancedTree a
    = Leaf a
    | Branch (PerfectlyBalancedTree (a,a))
    deriving (Eq, Ord, Show, Read)

在我们继续讨论好位之前,让我们先看看这种类型。以下是 type 的一些典型值PerfectlyBalancedTree Integer

Leaf 0
Branch (Leaf (0, 0))
Branch (Branch (Leaf ((0,0),(0,0))))
Branch (Branch (Branch (Leaf (((0,0),(0,0)),((0,0),(0,0))))))

实际上,您可以将这种类型的任何值可视化为 n 个Branch标签的初始序列,后跟“我们终于完成了,谢天谢地”Leaf标签,后跟包含类型的 2^n 元组。凉爽的。

现在,我们将编写一个函数来解析这些更方便的表示。这是几个示例调用:

*Main> :t fromString
fromString :: String -> PerfectlyBalancedTree Integer
*Main> fromString "0"
Leaf 0
*Main> fromString "b(42,69)"
Branch (Leaf (42,69))
*Main> fromString "bbb(((0,0),(0,0)),((0,0),(0,0)))"
Branch (Branch (Branch (Leaf (((0,0),(0,0)),((0,0),(0,0))))))

在此过程中,编写一个稍微多态的函数会很方便。这里是:

fromString' :: Read a => String -> PerfectlyBalancedTree a
fromString' ('b':rest) = Branch (fromString' rest)
fromString' leaf = Leaf (read leaf)

现在我们真正的函数只是具有不同类型签名的同一件事:

fromString :: String -> PerfectlyBalancedTree Integer
fromString = fromString'

但是等一下……这里刚刚发生了什么?我把你的东西滑倒了!为什么我们不直接写这个?

fromStringNoGood :: String -> PerfectlyBalancedTree Integer
fromStringNoGood ('b':rest) = Branch (fromStringNoGood rest)
fromStringNoGood leaf = Leaf (read leaf)

原因是在递归调用中,fromStringNoGood有不同的类型。它不是被要求返回 a PerfectlyBalancedTree Integer,而是被要求返回 a PerfectlyBalancedTree (Integer, Integer)。我们可以给自己写一个这样的函数......

fromStringStillNoGood :: String -> PerfectlyBalancedTree Integer
fromStringStillNoGood ('b':rest) = Branch (helper rest)
fromStringStillNoGood leaf = Leaf (read leaf)

helper :: String -> PerfectlyBalancedTree (Integer, Integer)
helper ('b':rest) = Branch ({- ... what goes here, now? -})
helper leaf = Leaf (read leaf)

...但是这种方式是无限倒退到编写越来越深的嵌套类型。

问题是,即使我们对单态顶级函数感兴趣,我们仍然无法静态确定read在它使用的多态函数中调用的是什么类型!我们传递的数据决定了read应该返回什么类型的元组:更多b的 sString意味着更深的嵌套元组。

于 2012-07-10T21:32:10.990 回答
5

你是对的:Haskell 确实有重载,它通过其类型类机制提供它。

考虑以下签名:

f :: [a] -> a
g :: Num a => [a] -> a

第一个签名告诉你,给定任何类型的元素列表af都会产生一个类型的值a。这意味着 的实现f不能对类型a及其允许的操作做出任何假设。这是参数多态性的一个例子。片刻的反思表明,实际上实现的选项很少f:您唯一能做的就是从提供的列表中选择一个元素。从概念上讲,有一个f适用于所有类型的通用实现a

a第二个签名告诉您,给定属于类型类的某种类型的元素列表Numg将产生该类型的值a。这意味着 的实现可以使用类型类附带的所有操作来g消费、生产和操作类型的值。例如,可以将列表的元素相加或相乘,选择列表的最小值,返回一个提升的常量,...其他主要形式是强制)。从概念上讲,.中的所有类型都有不同的实现。aNumggaNum

于 2012-07-10T19:20:04.030 回答
1

它具有返回类型重载。有关一个很好的示例,请参见 Read 函数。它的类型为 Read a => String -> a。它可以读取并返回任何实现读取类型类的东西。

于 2012-07-10T19:30:04.187 回答