我使用GHCI测试了数字强制:
>> let c = 1 :: Integer
>> 1 / 2
0.5
>> c / 2
<interactive>:15:1: error:
• No instance for (Fractional Integer) arising from a use of ‘/’
• In the expression: c / 2
In an equation for ‘it’: it = c / 2
>> :t (/)
(/) :: Fractional a => a -> a -> a -- (/) needs Fractional type
>> (fromInteger c) / 2
0.5
>>:t fromInteger
fromInteger :: Num a => Integer -> a -- Just convert the Integer to Num not to Fractional
我可以使用fromInteger
函数将Integer类型转换为Num(fromInteger的类型为fromInteger :: Num a => Integer -> a
),但我无法理解类型Num
如何转换为{{ 1}}含蓄地?
我知道如果实例的类型为Fractional
,则它必须具有类型Fractional
(Num
),但是如果实例的类型为class Num a => Fractional a where
,它是否必要用作Num
类型的实例?
@mnoronha感谢您的详细回复。只有一个问题让我困惑。我知道Fractional
无法在函数type a
中使用的原因是(/)
与type a
不是type Integer
的实例(函数{{ 1}}要求参数类型必须为type class Fractional
)。我不明白的是,即使通过调用(/)
将类型整数转换为instance of Fractional
类型的fromInteger
类型,也不意味着a
类型是Num
的实例(因为Fractional类型类比Num类型类更受约束,因此a
类型可能无法实现Fractional
所需的某些函数。如果a
类型不完全符合Fractional type class
所要求的条件,那么如何在函数a
中使用它,它要求参数类型为Fractional的实例。对不起,不是母语,非常感谢您的耐心等待!
我测试过,如果某个类型只适合父类型类,则不能在需要更多约束类型类的函数中使用它。
Fractional type class
我找到了一个非常清楚地解释数字重载歧义的文档,可能会帮助其他人同样困惑。
答案 0 :(得分:4)
首先,请注意Fractional
和Num
都不是类型,而是类型类。您可以在文档或其他地方阅读有关它们的更多信息,但基本思想是它们定义类型的行为。 Num
是最具包容性的数字类型类,定义了(+)
,negate
等行为函数,它们几乎适用于所有数字类型。&#34; Fractional
是一个更受约束的类型类,用于描述&#34;小数,支持实际除法。&#34;
如果我们查看Fractional
的类型类定义,我们会发现它实际上被定义为Num
的子类。也就是说,对于类型为a
且具有实例Fractional
的类型,它必须首先是类型类Num
的成员:
class Num a => Fractional a where
让我们考虑一些受Fractional
约束的类型。我们知道它实现了Num
所有成员共有的基本行为。但是,除非指定了多个约束(例如(Num a, Ord a) => a
),否则我们不能指望它实现其他类型类的行为。例如,使用函数div :: Integral a => a -> a -> a
(整数除法)。如果我们尝试使用受类型类Fractional
约束的参数(例如1.2 :: Fractional t => t
)来应用函数,我们遇到错误。键入类限制函数的值类型处理,允许我们为共享行为的类型编写更具体和有用的函数。
现在让我们看看更通用的类型类Num
。如果我们的类型变量a
仅受Num a => a
约束,我们知道它将实现Num
类型类定义中包含的(少数)基本行为,但我们&#39 ; d需要更多背景知识。这实际意味着什么?我们从Fractional
类声明中知道,Fractional
类型类中定义的函数适用于Num
类型。但是,这些Num
类型是所有可能的Num
类型的子集。
所有这一切的重要性最终都与地面类型有关(其中类型类约束在函数中最常见)。 a
表示一种类型,符号Num a => a
告诉我们a
是包含类型类Num
的实例的类型。 a
可以是包含该实例的所有类型(例如Int
,Natural
)。因此,如果我们给一个通用类型Num a => a
赋值,我们知道它可以为定义了类型类的每个类型实现函数。例如:
ghci>> let a = 3 :: (Num a => a)
ghci>> a / 2
1.5
如果我们将a
定义为特定类型,或者根据更受约束的类型类定义,我们将无法预期相同的结果:
ghci>> let a = 3 :: Integral a => a
ghci>> a / 2
-- Error: ambiguous type variable
或
ghci>> let a = 3 :: Integer
ghci>> a / 2
-- Error: No instance for (Fractional Integer) arising from a use of ‘/’
(编辑回复后续问题)
这绝对不是最具体的解释,因此读者可以随意提出更严谨的建议。
假设我们有一个函数a
,它只是id
函数的类型类约束版本:
a :: Num a => a -> a
a = id
让我们看看函数的某些应用程序的类型签名:
ghci>> :t (a 3)
(a 3) :: Num a => a
ghci>> :t (a 3.2)
(a 3.2) :: Fractional a => a
虽然我们的函数具有通用类型签名,但由于其应用程序,应用程序的类型更受限制。
现在,让我们看看函数fromIntegral :: (Num b, Integral a) => a -> b
。这里,返回类型是通用Num b
,无论输入如何都是如此。我认为考虑这种差异的最佳方式是精确度。 fromIntegral
采用更受约束的类型并使其受约束更少,因此我们知道我们总是期望结果将受签名中的类型类约束。但是,如果我们给出输入约束,实际输入可能比约束更受限制,结果类型将反映出来。
答案 1 :(得分:0)
这种工作的原因归结为通用量化的工作方式。为了帮助解释这一点,我将在明确的forall
中添加类型签名(如果您启用-XExplicitForAll
或任何其他forall
相关扩展,您可以自行添加),但如果您只是删除它们(forall a. ...
只变为...
),一切都会正常。
要记住的是,当一个函数涉及一个由类型类约束的类型时,那意味着你可以在该类型类中输入/输出任何类型,所以实际上更好的是受限制较少类型类。
所以:
fromInteger :: forall a. Num a => Integer -> a
fromInteger 5 :: forall a. Num a => a
表示您的值为Num
类型。所以你不仅可以在Fractional
中使用它的函数中使用它,只要有一个实现两个{{1}的单一类型,你就可以在只接受MyWeirdTypeclass a => ...
的函数中使用它。 }和Num
。因此,为什么你可以得到以下内容:
MyWeirdTypeclass
当然,当你决定除以2时,它现在希望输出类型为fromInteger 5 / 2 :: forall a. Fractional a => a
,因此Fractional
和5
将被解释为2
}类型,因此我们不会遇到我们尝试划分Fractional
值的问题,因为尝试使上面的类型Int
无法键入检查。
这非常强大且非常棒,但非常陌生,因为通常其他语言不支持此功能,或仅支持输入参数(例如Int
在大多数语言中都可以采用任何可打印的语言类型)。
现在你可能会对整个超类/子类的东西发挥作用感到好奇,所以当你定义一个类型为print
的函数时,那么因为用户可以传入任何{{1} } type,你是正确的,在这种情况下,你不能使用在Num a => a
的某个子类上定义的函数,只能处理所有Num
值的函数,例如Num
:
Num
因此,以下内容未进行类型检查,并且不会使用任何语言进行检查,因为您不知道该参数是*
。
double :: forall a. Num a => a -> a
double n = n * 2 -- in here `n` really has type `exists a. Num a => a`
我们上面提到Fractional
的内容更类似于以下更高级别的功能,请注意括号内的halve :: Num a => a -> a
halve n = n / 2 -- in here `n` really has type `exists a. Num a => a`
是必需的,您需要使用fromInteger 5 / 2
:< / p>
forall
从那时起,您每次都会-XRankNTypes
类型(就像您之前处理的halve :: forall b. Fractional b => (forall a. Num a => a) -> b
halve n = n / 2 -- in here `n` has type `forall a. Num a => a`
一样),而不仅仅是Num
类型。现在这个函数的缺点(以及没有人想要它的一个原因)是你真的必须传递每个fromInteger 5
类型的东西:
Num
我希望稍微澄清一下。 Num
工作所需要的只是一种类型,即halve (2 :: Int) -- does not work
halve (3 :: Integer) -- does not work
halve (1 :: Double) -- does not work
halve (4 :: Num a => a) -- works!
halve (fromInteger 5) -- also works!
和fromInteger 5 / 2
,或者换句话说只有Num
,因为{ {1}}隐含Fractional
。类型违约对清除这种混乱没有多大帮助,因为您可能没有意识到GHC只是随意挑选Fractional
,它可能选择了任何Fractional
。