如果不在 Eq 类中,Haskell 整数文字如何进行比较?
在 Haskell 中(至少在 GHC v8.8.4 中),在Num课堂上并不意味着在Eq课堂上:
$ ghci
GHCi, version 8.8.4: https://www.haskell.org/ghc/ :? for help
?>
?> let { myEqualP :: Num a => a -> a -> Bool ; myEqualP x y = x==y ; }
<interactive>:6:60: error:
• Could not deduce (Eq a) arising from a use of ‘==’
from the context: Num a
bound by the type signature for:
myEqualP :: forall a. Num a => a -> a -> Bool
at <interactive>:6:7-41
Possible fix:
add (Eq a) to the context of
the type signature for:
myEqualP :: forall a. Num a => a -> a -> Bool
• In the expression: x == y
In an equation for ‘myEqualP’: myEqualP x y = x == y
?>
这似乎是因为例如Num可以为某些功能类型定义实例。
此外,如果我们防止ghci过度猜测整数文字的类型,它们只有Num类型约束:
?>
?> :set -XNoMonomorphismRestriction
?>
?> x=42
?> :type x
x :: Num p => p
?>
因此,上面的 x 或 42 之类的术语没有可比性。
但是,它们碰巧是:
?>
?> y=43
?> x == y
False
?>
有人可以解释一下这个明显的悖论吗?谢谢。
回答
如果不使用Eq. 但这也不是正在发生的事情。
在 GHCi 中, under NoMonomorphismRestriction(现在是 GHCi 中的默认值;在 GHC 8.8.4 中不确定)x = 42导致x类型为 的变量forall p :: Num p => p。1
然后你这样做y = 43,这同样会导致变量y具有 type forall q. Num q => q。2
然后您输入x == y,GHCi 必须进行评估才能打印True或False。如果不为p和q(必须相同)选择具体类型,就无法进行评估。每种类型都有自己定义 的代码==,因此无法在==不决定使用哪种类型的代码的情况下运行代码。3
然而每一个x和y可以使用的任何类型的Num(因为他们有他们所有的工作定义)4。所以我们可以直接使用(x :: Int) == y,编译器会确定它应该使用Intfor的定义==,还是x == (y :: Double)使用该Double定义。我们甚至可以用不同的类型重复执行此操作!这些用法都不会改变x或的类型y;我们每次只是在它们支持的(许多)类型之一中使用它们。
如果没有defaulting的概念,barex == y只会Ambiguous type variable从编译器中产生错误。语言设计者认为这对于数字文字来说是非常常见和极其烦人的(因为文字是多态的,但是一旦您对它们进行任何操作,您就需要一个具体的类型)。因此,他们引入了一些规则,如果允许编译继续,则应将某些模棱两可的类型变量默认为具体类型。5
因此,当您这样做时实际发生的x == y是编译器只是选择在该特定表达式中Integer使用 forx和y,因为您没有提供足够的信息来确定任何特定类型(并且因为默认规则适用于这种情况) . Integer有一个Eq实例,所以它可以使用它,即使最通用的类型x和y不包括Eq约束。如果不选择某些东西,它甚至无法尝试调用==(当然,它选择的“东西”必须在其中,Eq否则它仍然无法工作)。
如果您打开-Wtype-defaults(包含在 中-Wall),编译器将在应用默认值6时打印警告,这使得该过程更加可见。
1该forall p部分在标准 Haskell 中是隐含的,因为所有类型变量都自动forall在它们出现的类型表达式的开头引入。您必须打开扩展才能forall手动编写;要么ExplicitForAll只是为了编写的能力forall,要么是许多扩展中的任何一个,这些扩展实际上添加了对forall显式编写有用的功能。
2 GHCi 可能会p再次选择类型变量,而不是q. 我只是使用不同的变量来强调它们是不同的变量。
3从技术上讲,并不是每种类型都必须有不同的==,而是每个Eq实例。其中一些实例是多态的,因此它们适用于多种类型,但这仅适用于具有某种结构的类型(如Maybe a等)。Int, Integer, Double, Char,等基本类型Bool都有自己的实例,而这些实例中的每一个都有自己的 代码==。
4在底层系统中,类型likeforall p. Num p => p其实很像一个函数;一种将Num具体类型的实例作为参数的方法。要获得一个具体的值,你必须首先“应用函数”到一个类型的Num实例上,然后你才能得到一个可以打印的实际值,与其他东西进行比较等等。在标准的 Haskell 中,这些实例参数总是不可见地传递被编译器包围;一些扩展允许你更直接地操作这个过程。
这是何等的混乱,为什么根x == y工作时,x和y多态变量。如果您必须显式地传递类型/实例参数,那么这里发生的事情就很明显了,因为您必须手动将x和应用y到某物并比较结果。
5默认规则的要点是,如果对不明确类型变量的约束是:
- 所有内置类
- 它们中的至少一个是数字类(
Num,Floating等)
然后 GHC 将尝试Integer查看该类型是否检查并允许解决所有其他约束。如果这不起作用,它将尝试Double,如果这不起作用,则报告错误。
您可以使用default声明设置它将尝试的类型(“默认默认值”为default (Integer, Double)),但您无法自定义它将尝试默认事物的条件,因此根据我的经验,更改默认类型的用途有限。
然而,GHCi 带有扩展的默认规则,这些规则在解释器中更有用(因为它必须逐行进行类型推断,而不是一次对整个模块进行类型推断)。您可以在带有ExtendedDefaultRules扩展名的编译代码中打开它们(或在 GHCi 中使用 关闭它们NoExtendedDefaultRules),但同样,根据我的经验,这两个选项都不是特别有用。解释器和编译器的行为不同很烦人,但是一次模块编译和一次一行解释之间的根本区别意味着将任何一个的默认规则切换为与另一个一致的工作更加烦人。(这也是为什么NoMonomorphismRestriction现在在解释器中默认生效;单态限制在编译代码中实现其目标方面做得不错,但在解释器会话中几乎总是错误的)。
6你也可以组合使用类型化的孔与该asTypeOf助手得到GHC告诉你键入它的推断像这样的子表达式:
? :t x
x :: Num p => p
? :t y
y :: Num p => p
? (x `asTypeOf` _) == y
<interactive>:19:15: error:
• Found hole: _ :: Integer
• In the second argument of ‘asTypeOf’, namely ‘_’
In the first argument of ‘(==)’, namely ‘(x `asTypeOf` _)’
In the expression: (x `asTypeOf` _) == y
• Relevant bindings include
it :: Bool (bound at <interactive>:19:1)
Valid hole fits include
x :: forall p. Num p => p
with x
(defined at <interactive>:1:1)
it :: forall p. Num p => p
with it
(defined at <interactive>:10:1)
y :: forall p. Num p => p
with y
(defined at <interactive>:12:1)
Found hole: _ :: Integer在继续处理它喜欢提供给我们的关于错误的所有额外信息之前,您可以看到它告诉我们很好和简单。
打字孔(以其最简单的形式)仅意味着用文字_代替表达式。编译器会在这样的表达式上出错,但它会尝试为您提供有关可以用来“填空”以使其编译的信息;最有用的是,它告诉你在那个位置上有效的东西的类型。
foo `asTypeOf` bar是添加一些类型信息的旧模式。它返回foo但它限制(这种特殊用法)它与bar(的实际值bar完全未使用)相同的类型。因此,如果您已经有一个d类型为 的变量Double,x `asTypeOf` d则将x作为 a的值Double。
在这里,我使用asTypeOf“向后”;而不是使用右边的东西来限制左边东西的类型,我在右边放了一个洞(可以有任何类型),但asTypeOf方便地确保它是相同的类型而x不用其他方式改变如何x是在整个表达式中使用(因此相同的类型推断仍然适用,包括默认值,如果您将较大表达式的一小部分取出以向 GHCi 询问其类型,情况并非总是如此:t;特别是:t x不会告诉我们Integer,但是Num p => p)。