基于我读过的关于Haskell的内容,以及我用GHC做过的实验,似乎Haskell有返回类型重载(又称ad hoc多态)。其中一个示例是fromInteger
函数,根据结果的使用位置,它可以为您提供Double
或Integer
。例如:
fd :: Double -> String
fd x = "Double"
fi :: Integer -> String
fi x = "Integer"
fd (fromInteger 5) -- returns "Double"
fi (fromInteger 5) -- returns "Integer"
A Gentle Introduction to Haskell似乎同意这一点:
到目前为止,我们所讨论的多态性通常称为参数多态。另一种称为ad hoc多态,更好地称为重载。以下是ad hoc多态的一些示例:
- 文字1,2等通常用于表示固定和任意精度整数。
如果数字文字被认为是ad hoc多态(也就是重载)的一个例子,那么对fromInteger
等函数的结果似乎也是如此。
事实上,我发现other questions on Stack Overflow的一些答案表明Haskell已经按返回类型进行了重载。
但是,至少有一个Haskell程序员告诉我这不是返回类型重载,而是“参数多态,其中参数受通用量词限制”的例子。
我认为他得到的是fromInteger
从Num
的每个实例返回一个值(一种非确定性类型)。
这似乎是一个合理的解释,但据我所知,Haskell从不让我们看到这些实例值中的多个(部分归功于Monomorphism restriction)。我们看来的实际情况似乎也可以静态确定。由于所有这一切,似乎可以合理地说,在表达式fd (fromInteger 5)
中,子表达式fromInteger 5
的类型为Double
,而在表达式fi (fromInteger 5)
中的子表达式{{1}类型为fromInteger 5
。
那么,Haskell是否有返回类型重载?
如果没有,请提供以下其中一项的示例:
答案 0 :(得分:16)
好吧,一种看待它的方法是,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
的{{1}}值,以及使用fromInteger :: Integer -> T
的所有代码将type类翻译成以字典作为参数的代码。
答案 1 :(得分:12)
关于Haskell风格类型类的开创性论文称为"How to make ad-hoc polymorphism less ad hoc"。所以,你的问题的答案是合格的“是” - 取决于你需要返回类型重载的临时性......
换句话说:毫无疑问,ad hoc多态与<相关与类型类相关,因为这是发明它们的一个激励性例子。但是,您是否认为结果仍然符合“返回类型重载”的要求取决于您喜欢的定义的细节。
答案 2 :(得分:12)
我想谈谈你问题的一小部分:
我们看来的实际情况似乎也可以静态确定。
这不是很准确。考虑以下古怪的数据类型:
data PerfectlyBalancedTree a
= Leaf a
| Branch (PerfectlyBalancedTree (a,a))
deriving (Eq, Ord, Show, Read)
在我们转向好位之前,先让我们先看一下这种类型。以下是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-tuple。凉。
现在,我们将编写一个函数来解析这些更方便的表示。这是一些示例调用:
*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
具有不同的类型。它没有被要求返回PerfectlyBalancedTree Integer
,而是要求它返回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
中的String
更多意味着更深层次的嵌套元组。
答案 3 :(得分:4)
你是对的:Haskell确实有重载,它通过它的类型机制提供它。
考虑以下签名:
f :: [a] -> a
g :: Num a => [a] -> a
第一个签名告诉您,如果给定任何类型a
的元素列表,f
将生成类型a
的值。这意味着f
的实现不能对类型a
及其承认的操作做出任何假设。这是参数多态的示例。片刻的反思揭示了实现f
的实际选择很少:你唯一能做的就是从提供的列表中选择一个元素。从概念上讲,f
的单一通用实现适用于所有类型a
。
第二个签名告诉您,给定属于类型类a
的某种类型Num
的元素列表,g
将生成该类型{{1}的值}。这意味着a
的实现可以使用类型类g
附带的所有操作来使用,生成和操作类型a
的值。例如,Num
可以添加或乘以列表的元素,选择列表的最小值,返回提升的常量,......这是重载的示例,通常是被认为是ad-hoc多态的一种形式(另一种主要形式是强制)。从概念上讲,g
中所有类型g
的{{1}}实现了不同的实现。
答案 4 :(得分:1)
它有返回类型重载。有关示例,请参阅读取功能。它的类型为Read a =&gt;字符串 - &gt;一个。它可以读取和返回任何实现读类型类的内容。