【问题标题】:Call an instance "method" in haskell在haskell中调用实例“方法”
【发布时间】:2016-10-17 02:55:46
【问题描述】:

我正在阅读 Haskell Programming from first principle 这本书,在其中一个建议的练习中,作者要求我们为 编写一个类 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),就是这样,我得到了一个布尔值的答案。

谁能给我一个提示?

【问题讨论】:

  • tooMany (10, 10)? (此外,在 GHCi 中尝试 :t (10, 10) 并仔细检查它是否适合您的代码。)
  • 不,这行不通。 i.imgur.com/3Z2bFLu.png 另外,:t (10, 10) 返回 :: (Num t1, Num t) => (t, t1)
  • @dfeuer 是正确的,因为需要 FlexibleInstances ——尽管您可能已经启用了它,因为您至少能够定义实例。除此之外,确实有必要通过例如为元组赋予适当的特殊类型。 (10 :: Int, 10 :: Int)。确实是一个相当奇怪的练习。

标签: haskell


【解决方案1】:

首先,正如@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 中实际上并没有单态类型。 10Num a => a 类型,不是 Int 也不是 Integer 也不是 Double 或其他类似的东西。因为类型类是开放的,即使 只为 TooMany(即 Int)定义了一个 Num 类型的实例,Haskell 编译器也不能依赖它来推断存在事实上,TooMany 只有一个Num 类型实例,因此(10, 10) 必须具有(Int, Int) 类型。使用您的代码的其他任何人都可以为Doubles 定义自己的TooMany 实例。

特别是,您的模块的使用者可以为TooManyNum 创建一个带有退化类型实例的新类型。

-- 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 的行为,必须使用类型注解。

【讨论】:

    【解决方案2】:

    根据错误消息,您似乎不仅使用了FlexibleInstances,甚至还使用了OverlappingInstances。正如 duplode 所指出的,您必须显式地修复精确的参数类型才能使用元组调用方法。在选择实例之前,GHC 必须能够看到元组既不是 (Int, String) 也不是 (Int, Int),并且组件具有相同的类型。

    通常不鼓励这种临时类,并且更强烈地不鼓励这种完全疯狂的实例。一般来说,类实例应该非常统一。如果您需要多个元组实例,那么您可能做错了什么。重叠实例本质上是针对类型导向的元编程。我个人像避免瘟疫一样避免它们;如果你要使用它们,你需要非常非常小心你正在做的事情,并认识到在某些情况下产生的 API 可能会以意想不到的方式运行。有一些技术可以避免使用类型族的重叠,从而使复杂的实例结构不那么狂野,但即使是那些也需要非常小心地设计,并且会限制你在以后可能会咬你的方式。

    【讨论】:

    • 我不使用OverlappingInstances。至少重叠对我来说并不明显,错误消息似乎与 FlexibleInstances 一致。
    • @badcook,错误消息中描述的实例在第 63 行和第 66 行,对于 (Int, Int)(a, a),肯定是重叠的。
    • 你是完全正确的。我浏览图片的速度太快了。
    猜你喜欢
    • 2016-08-30
    • 2012-09-27
    • 2013-04-15
    • 1970-01-01
    • 1970-01-01
    • 2014-11-21
    • 2012-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多