Haskell 具有Num类型类强制的实例如何隐式地转换为分数?

Haskell 具有Num类型类强制的实例如何隐式地转换为分数?,haskell,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 >

我使用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

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


首先,请注意,
fractive
Num
都不是类型,而是类型类。您可以在文档或其他地方阅读更多关于它们的内容,但基本思想是它们定义类型的行为
Num
是最具包容性的数字类型类,它定义了行为函数,如
(+)
否定
,这几乎是所有“数字类型”所共有的。
分数
是一个更受约束的类型类,它描述了“分数数字,支持实数除法”

如果我们查看
fractive
的类型类定义,我们会发现它实际上是定义为
Num
的一个子类。也就是说,要使类型
a
成为具有实例
分数
,它必须首先是typeclass
Num
的成员:

class Num a => Fractional a where

让我们考虑一下由<代码>分数< /代码>约束的一些类型。我们知道它实现了

Num
所有成员通用的基本行为。但是,除非指定了多个约束(例如
(Num a,Ord a)=>a
,否则我们不能期望它实现来自其他类型类的行为。例如,函数
div::Integral a=>a->a
(整数除法)。如果我们尝试使用受typeclass约束的参数应用函数
fractive
(例如
1.2::fractive t=>t
),我们遇到了一个错误。类型类限制函数处理的值的种类,允许我们为共享行为的类型编写更具体、更有用的函数

现在让我们看一看更通用的类型类,
Num
。如果我们有一个类型变量
a
,它只受
numa=>a
的约束,我们知道它将实现(很少)
Num
类型类定义中包含的基本行为,但我们需要更多的上下文来了解更多。这实际上意味着什么?我们从
fractive
类声明中知道
fractive
类型类中定义的函数应用于
Num
类型。但是,这些
Num
类型re是所有可能的
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


(编辑对后续问题的回答)

这肯定不是最具体的解释,所以读者可以自由地提出更严格的建议

假设我们有一个函数
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
让我们看看函数的一些应用程序的类型签名:

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
。在这里,返回类型是general
Num b
,无论输入如何,这都是正确的。我认为考虑这种差异的最佳方式是精度。
from integral
采用更受约束的类型,使其约束更少,因此我们知道我们总是希望结果受到但是,如果我们给出一个输入约束,实际输入可能比约束更受限制,结果类型将反映这一点。

这一工作的原因归结为通用量化的工作方式。为了帮助解释这一点,我将在类型si中添加显式的
forall
gnatures(如果启用
-XExplicitForAll
或任何其他
forall
相关扩展,您可以自己执行),但是如果您只是删除了它们(
forall a…
变成了
),一切都会正常工作

需要记住的是,当一个函数涉及一个受typeclass约束的类型时,这意味着你可以输入/输出该typeclass中的任何类型,所以实际上最好使用约束较少的typeclass

因此:

这意味着您有一个属于每种
Num
类型的值。因此,您不仅可以在函数中使用它,而且可以在函数中使用它,只要有一种类型同时实现
Num
MyWeirdTypeclass
,您就可以在函数中使用它。因此,您可以很好地获得以下信息:

fromInteger 5 / 2 :: forall a. Fractional a => a
当然,一旦您决定除以2,它现在希望输出类型是
fractive
,因此
5
2
将被解释为一些
fractive
类型,因此我们不会遇到试图除以
Int
值的问题,因为试图使上述类型具有
Int
将失败我需要打字检查

这是非常强大和可怕的,但非常陌生,因为通常其他语言要么不支持它,要么只支持输入参数(e)
fromInteger 5 / 2 :: forall a. Fractional a => a
double :: forall a. Num a => a -> a
double n = n * 2  -- in here `n` really has type `exists a. Num a => a`
halve :: Num a => a -> a
halve n = n / 2  -- in here `n` really has type `exists a. Num a => a`
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`
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!