我正在阅读 Haskell编程从第一原理这本书,并且在提议的一个练习中,作者要求我们为编写一个类 TooMany 的实例>(Num a,TooMany a)=> (a,a)如果两个数字的总和大于42,则应进行比较。
这是我的代码:
class TooMany a where
tooMany :: a -> Bool
instance TooMany Int where
tooMany n = n > 42
instance (Num a, TooMany a) => TooMany(a, a) where
tooMany (a, b) = tooMany (a + b)
我认为我的代码是正确的,但我不知道如何编写表达式来测试它。对于 TooMany Int 我只是在REPL中写了 tooMany(10 :: Int),就是这样,我有一个布尔答案。
任何人都可以给我一个提示吗?
答案 0 :(得分:5)
根据错误消息,您似乎不仅使用FlexibleInstances
,还使用OverlappingInstances
。正如duplode所示,您必须显式修复精确的参数类型才能使用元组调用该方法。在选择实例之前,GHC必须能够看到元组既不是(Int, String)
也不是(Int, Int)
,并且组件具有相同的类型。
这种临时课程通常是不鼓励的,而且更加强烈地劝阻这些完全疯狂的实例。通常,类实例应该非常统一。如果元组需要多个实例,那么你可能做错了什么。重叠实例主要用于类型导向的元编程。我个人像瘟疫一样避开它们;如果你打算使用它们,你需要非常非常小心你正在做的事情,并认识到在某些情况下产生的API可能会以意想不到的方式表现出来。有一些技术可以避免重复使用类型系列,使复杂的实例结构变得不那么狂野,但即使是那些需要非常谨慎地设计并且会限制你以后可能会咬你的方式。
答案 1 :(得分:5)
首先,正如@dfeuer恰当地指出的那样,在“真实的”Haskell编程中,你不会想要将类型类用于这类东西。 The Haskell community generally wants typeclasses with a set of meaningful algebraic laws that govern the behavior of the typeclass instances。编译器不检查这些定律(虽然程序员经常会为它们编写QuickCheck tests)。
有了这个,我假设这本书正在教你这纯粹是为了让你熟悉类型类的机制(他们可以有引用类型类本身的约束)而不是支持这个类型类特别好之一。
@duplode为您提供了有关在此方案中执行的操作的正确答案:注释您的类型。
Main> tooMany (10 :: Int, 10 :: Int)
False
那么为什么GHC不能自动为(10, 10)
找到正确的实例而需要类型注释呢?从根本上说,这是因为Haskell类型类是开放的,这意味着任何人都可以在任何时候创建一个你在另一个你不知道的模块中创建的类型类的新实例,例如:如果你把这个类型类放在一个库中,那个库的下游消费者可以自己创建这个类型类的实例。
在这种情况下,正如您已经注意到的那样,数字文字在Haskell中实际上并不具有单形类型。 10
类型为Num a => a
,不是Int
,也不是Integer
,也不是Double
,也不是其他类似内容。因为类型类是开放的,即使你只为Num
定义了TooMany
类型的单个实例(即Int
),Haskell编译器也不能依赖据推断,Num
实际上只有一个TooMany
类型的实例,因此(10, 10)
必须具有(Int, Int)
类型。使用您代码的任何其他人都可以为TooMany
s定义自己的Double
实例。
特别是,您的模块的使用者可以使用TooMany
和Num
的简并类型实例创建一个新类型。
-- Imagine you published `TooMany` as a library on Hackage
-- and now a downstream consumer writes the following
-- This example happens to break the laws for Num,
-- but the compiler doesn't know about typeclass laws
data DegenerateType = DegenerateType
instance Num DegenerateType where
_ + _ = DegenerateType
_ * _ = DegenerateType
negate _ = DegenerateType
abs _ = DegenerateType
signum _ = 0
-- The next line dictates what a numeric literal
-- actually means for a DegenerateType
fromInteger _ = DegenerateType
instance TooMany DegenerateType where
tooMany _ = True
现在tooMany (10, 10)
具有不同的行为,具体取决于单态类型10
。
Main> tooMany (10 :: DegenerateType, 10 :: DegenerateType)
True
因此,tooMany (10, 10)
不足以指定tooMany
的行为,您必须使用类型注释。