在 ghci 中:
:t (*)
(*) :: Num a => a -> a -> a
:t (/)
(/) :: Fractional a => a -> a -> a
Run Code Online (Sandbox Code Playgroud)
为什么除法需要小数输入?我的意思是,我理解“为什么”(因为它是为了这样做而编写的),但我不明白为什么它是这样实现的?*可以接受和返回小数,为什么不能/ ?我知道div并且quot存在,但我不明白为什么/不会像看起来那样转换它的参数*(或者为什么它不会根据给定的参数成为 div/quot 的别名。
我确信这是有原因的,只是我不明白它是什么?
luq*_*qui 10
我想您可能对限制的含义感到困惑。这些操作中都不会发生自动转换。约束本质上是一组支持此操作的类型。 Num是最基本的数值类,其成员包括Integer、Double、Rational等,这意味着(*)可以有以下任意类型:
(*) :: Integer -> Integer -> Integer\n(*) :: Double -> Double -> Double\n(*) :: Rational -> Rational -> Rational\nRun Code Online (Sandbox Code Playgroud)\n请特别注意,所有三种类型都是相同的 - 您必须输入两个相同类型,并且总是会返回您输入的内容。
\n一般来说,你不能将两个整数相除并得到一个整数,所以我们不能允许
\n(/) :: Integer -> Integer -> Integer\nRun Code Online (Sandbox Code Playgroud)\n这就是为什么有更具体的Fractional数字类型类,这些类在除法下封闭。
正如您所建议的,可以在整数情况下(/)为 制作一个别名。div事实上, \xe2\x80\x93\xe2\x80\x93是可能的,你可以在源文件中写入:
instance Fractional Integer where\n (/) = div\n fromRational = floor\nRun Code Online (Sandbox Code Playgroud)\n现在Integers支持(/)整数除法运算符,就像其他所有语言一样!我强烈建议不要这样做,我认为 Haskell 语言设计者在这一点上是正确的。抛开允许您0.5在整数上下文中编写文字并让它默默地解释为零而不是错误(令人担忧的许多语言都允许!)这一完全疯狂的想法不谈,整数除法是一种与除法非常不同的操作。除法是乘法的逆运算,它具有以下良好的定律(尽管有 IEEE 舍入误差):
(a / b) * b = a\n (a + b) / c = (a / c) + (b / c)\nRun Code Online (Sandbox Code Playgroud)\n整数除法不是也不是。因此,如果(/)支持整数,那么如果您有一些多态算术函数:
someFormula x y = x / (x^2 + y^2) + y / (x^2 + y^2)\nRun Code Online (Sandbox Code Playgroud)\n你会说,嘿,这没有必要复杂,让我简化一下!
\nsomeFormula x y = (x + y) / (x^2 + y^2)\nRun Code Online (Sandbox Code Playgroud)\n您刚刚为决定在整数上使用的用户引入了一个错误someFormula,而该用户从未打算这样做,因为在这种情况下除法不是“真正的”除法(没有双关语),并且您的代数是错误的。所以这就是为什么我们必须Fractional表明这是一个诚实类型的支持除法,也就是说,乘法的逆。
(*) :: Num a => a -> a -> a意味着您可以选择任何a您喜欢的类型,它是 的成员Num,并且*可以是一个接受该类型的两个参数并返回相同类型的结果的函数。
必须为作为Num单独实现的成员的每个类型提供类型类的工作方式。*因此,有一个函数(*) :: Integer -> Integer -> Integer,另一个函数Double -> Double -> Double,另一个函数Ratio Word32 -> Ratio Word32 -> Ratio Word32,等等,*可以实例化为任何这些单独的函数。
类似地,(/) :: Fractional a => a -> a -> a意味着你可以选择任何类型a,但这里它必须是类型类的成员Fractional,这是更严格的。Double是 的成员Fractional,所以有一个(/) :: Double -> Double -> Double函数。Integer只是不是 的成员Fractional,所以没有(/) :: Integer -> Integer -> Integer函数。
Integer不属于 的原因Fractional是整数的数学除法根本不会产生整数。您需要一种支持整数之间的小数的类型,以便除法更有意义。有您可以对整数执行不同的操作,例如div,quot因此它们适用于Integer和 等类型Int。如果您想要其中一种操作,您应该要求它,而不是要求/并期望它在您对整数使用它时为您提供不同的操作。尤其是因为它如何知道您是否div认为quot“我可以用整数做的最接近除法的事情”?如果您有Int、Integer或类似的数字,并且想将其转换为小数,以便可以用它进行除法并获得小数结果,那么您需要使用像 ; 这样的转换函数fromIntegral。Haskell 不会神奇地为你插入这个,但它有所有的翻译功能,而且它们工作得很好。
请注意,调用时不会发生任何转换*;不乐意*以不乐意的方式转换论证/。Int的版本* 您为其提供两个Int参数,该Double版本要求您为其提供两个Double参数,等等。如果您调用Double的版本*但将其传递给它Int,则它不会将 转换Int为 a Double:您将收到类型错误。
新的 Haskellers 常常认为必须进行转换,因为他们可以计算 、 之类的表达式3.2 * 7,否则这怎么可能呢?
在其他几种流行的语言中,这里发生的情况是,语言实现说这3.2是一个浮点数并且7是一个整数,并且 - 就像在 Haskell 中一样 - 乘法只能用相同类型的两个参数来计算。因此,语言实现默默地插入一些代码来将整数转换为浮点类型;你得到的实际上是类似的东西3.2 * (floatFromInt 7)。但是,仅仅因为其他语言就是这样处理简单的算术表达式,并不意味着它是唯一的方法,当你开始学习 Haskell(或你开始学习的任何其他语言)时,最好了解 Haskell(或你开始学习的任何其他语言)实际上是如何处理这些表达式的。语言,而不是假设它必须像任何其他语言一样。
在 Haskell 中,编译器不会首先假设数字文字如3.2或7是任何特定类型。Haskell 更灵活。相反,源代码中的任何类似整数的数字都可以读取为任何数字类型( 中的任何类型Num)的文字,而源代码中的小数点数字可以读取为 中的任何类型的文字Fractional。*Haskell通过查看所使用的上下文(使用结果的代码)以及参数来确定正在调用的版本。如果这些事情中的任何一个强制使用单一类型,那么两个参数以及结果都必须能够是该单一类型。
因此,在类似的情况下3.2 * 7,结果的使用方式可能会决定结果必须是什么类型,这反过来又决定了该表达式中的类型*,进而决定了每个数字文字的读取方式。两者都将作为同一类型1的文字读入。在这种情况下,我们甚至可以确定7肯定不是、 Int或Integer或类似的任何类型,因为这些类型没有Fractional实例,并且使用的类型必须是 1 ,Fractional以便参数之一使用3.2包含小数点的文字。
1 . 当然,“作为任何数字类型的文字读入”实际上意味着整数文字使 Haskell 创建一个Integer,然后fromInteger :: Num a => Integer -> a来自适当类型Num实例的函数可以从 构建“真实”值Integer。类似地,小数点文字最初被读取为类型值Rational,然后fromRational :: Fractional a => Rational -> a使用该函数构建实际值。
因此,如果您非常挑剔和/或技术性很强,您可以说这3.2实际上始终是固定类型的文字,然后始终应用Rational转换函数。这并没有错,但我将其视为“是任何类型的文字”概念的实现策略。无论如何,函数的应用程序几乎总是在编译时内联,因此在运行时只会有最终所需类型的值。fromRational3.2Fractional
我认为这种观点对新的 Haskellers 更有帮助,因为很多人对整数和浮点类型之间的自动类型转换感到困惑,就像在其他语言中一样,但事实绝对不是这样;在其他语言中,整型变量也会发生这种情况。Haskell 的这种形式的转换仅与文字有关,而且它相当不同,因为fromRational(或fromInteger当您使用文字时总是) 翻译器,它与提升一种类型的真实运行时值以匹配另一个操作数的类型。