具有Num类型类的实例如何隐式地强制到Fractional?

时间:2016-11-12 06:57:37

标签: haskell

我使用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,则它必须具有类型FractionalNum),但是如果实例的类型为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

我找到了一个非常清楚地解释数字重载歧义的文档,可能会帮助其他人同样困惑。

https://www.haskell.org/tutorial/numbers.html

2 个答案:

答案 0 :(得分:4)

首先,请注意FractionalNum都不是类型,而是类型类。您可以在文档或其他地方阅读有关它们的更多信息,但基本思想是它们定义类型的行为。 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可以是包含该实例的所有类型(例如IntNatural)。因此,如果我们给一个通用类型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 ,因此Fractional5将被解释为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