我正在经历"真实世界Haskell"我正在做与之相关的练习。
我注意到一些我觉得奇怪的东西。
以此功能为例:
myAverage :: (Fractional a) => [a] -> Maybe a
myAverage [] = Nothing
myAverage xs = Just $ (mySum xs) / (fromIntegral $ myLength xs)
(/)
函数需要两个Fractional
实例的参数。
myLength
返回Int
,因此我使用the fromIntegral
函数,因为我被告知。
但fromIntegral
仅保证返回的值将是Num
的实例。
为什么Num
也包含"包含"在这种情况下,Fractional
的实例。不是Fractional
更具体吗?
为什么编译器没有抱怨我只有一个来自返回类型的Num
而不是"正确的" Fractional
。
为什么我不能直接使用Int
,它也是Num
的实例,不是吗?
在调用mySum
的情况下,我得到它。它需要一个Num
的列表,但我用Fractional
的列表(也是Num
s)来提供它,因为它们是从它派生的。返回类型具有相同的类型,从类型注释(见下文)可以看出:Fractional
但是在fromIntegral
的情况下,我无法推断自己(编译器显然可以:-))返回的值也是Fractional
的实例。为什么?
它所采用的类型显然不是Fractional
。
整个功能按预期工作。
只是为了澄清:
mySum :: (Num a) => [a] -> a
myLength :: [a] -> Int
谢谢,
拉扎勒斯
答案 0 :(得分:10)
fromIntegral
不会产生一些未知类型,我们只知道它是Num
的一个实例。 fromIntegral
生成我们(通常隐式)要求的任何类型,只要它是Num
的实例。
就OO语言而言,您需要将其视为泛型/模板,而不是虚拟继承/子类型多态。这就是fromIntegral
的等效Java签名看起来像<A extends Integral, B extends Num> B fromIntegral(A i)
,而不是Num fromIntegral(Integral i)
。
因此,如果我们想要Int
,我们会得到Int
。如果我们想要Integer
,我们会得到Integer
。如果我们想要Double
,我们会得到Double
。但是,如果我们想要String
,我们就不幸运了,因为String
不是Num
的实例。
在这种情况下,我们需要a
,其中a
是给定列表的元素类型。因此,如果我们传入Double
s列表,fromIntegral
会向我们提供Double
,如果我们传入Rational
s列表,fromIntegral
就会给我们Rational
。
答案 1 :(得分:6)
你问:
但是,只有整合才保证返回的值将是Num的实例。
这是真的,但要考虑fromIntegral
的类型:
fromIntegral :: (Integral a, Num b) => a -> b
因此,fromIntegral
允许我们将Int
转换为任何其他Num实例。
目前,让我们处理具体类型而不是类型类。假设你已经像这样定义了myAverage
:
myAverage :: [Double] -> Maybe Double
myAverage [] = Nothing
myAverage xs = Just $ mySum xs / (fromIntegral (myLength xs))
Double
支持分组,fromIntegral
能够将从Int
返回的myLength
转换为Double
。
如果您使用Rational
代替Double
,则相同的定义也适用,例如:
myAverage :: [Rational] -> Maybe Rational
返回类型类及其类型检查方式:
myAverage :: Fractional a => [a] -> Maybe a
...
myAverage xs = Just $ mySum xs / (fromIntegral (myLength xs))
这是怎么回事:
a
是Fractional,而Fractional是Num的子类,因此a
也是Num。xs
的类型为[a]
,因此mySum xs
的类型为a
myLength xs
的类型为Int
fromIntegral
可以将Int
转换为任何其他Num实例,因此可以将Int
转换为a
mySum xs
和fromIntegral表达式的类型a
都是小数,因此我们可以执行除法。