Haskell 为异构操作创建类型类时出现问题

Haskell 为异构操作创建类型类时出现问题,haskell,typeclass,functional-dependencies,Haskell,Typeclass,Functional Dependencies,下面是我试图做的一个非常简化的版本 假设我想创建一个可以接受不同类型操作数的通用差分运算 class Diff a b c where diff :: a -> b -> c 当然,我们可以将此操作应用于数字 instance Num a ⇒ Diff a a a where diff = (-) 但不仅仅是数字。如果我们有两个时间点,那么它们之间的差异就是一个时间间隔 newtype TimePoint = TP Integer deriving Show --

下面是我试图做的一个非常简化的版本

假设我想创建一个可以接受不同类型操作数的通用差分运算

class Diff a b c where
    diff :: a -> b -> c
当然,我们可以将此操作应用于数字

instance Num a ⇒ Diff a a a where
    diff = (-)
但不仅仅是数字。如果我们有两个时间点,那么它们之间的差异就是一个时间间隔

newtype TimePoint = TP Integer deriving Show -- seconds since epoch
newtype TimeInterval = TI Integer deriving Show -- in seconds

instance Diff TimePoint TimePoint TimeInterval where
    diff (Tp x) (Tp y) = TI (x-y)
一切都很好。除非我尝试在GHCi中测试我的
diff
,否则我会得到以下结果:

*Example λ diff 5 3

<interactive>:1:1: error:
    • Could not deduce (Diff a0 b0 c)
      from the context: (Diff a b c, Num a, Num b)
        bound by the inferred type for ‘it’:
                   forall a b c. (Diff a b c, Num a, Num b) => c
        at <interactive>:1:1-8
      The type variables ‘a0’, ‘b0’ are ambiguous
    • In the ambiguity check for the inferred type for ‘it’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      When checking the inferred type
        it :: forall a b c. (Diff a b c, Num a, Num b) => c
*Example λ
现在应该可以确定结果的类型了!不幸的是,这无法编译:

[1 of 1] Compiling Example          ( Example.hs, interpreted )

Example.hs:8:10: error:
    Functional dependencies conflict between instance declarations:
      instance Num a => Diff a a a -- Defined at Example.hs:8:10
      instance Num a => Diff (TimePoint a) (TimePoint a) (TimeInterval a)
        -- Defined at Example.hs:14:10
  |
8 | instance Num a => Diff a a a where
  |          ^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.
Prelude GOA λ 
顺便说一句,我也尝试过用关联类型族来代替FundeP,可以预见到类似的结果

现在我完全明白为什么会这样。有两个实例
Diff a a
Diff(TimePoint a)(TimePoint a)(TimeInterval a)
,它们不能与fundep共存。问题是,我如何解决这个问题?用新类型包装数字不是一个可行的解决方案,我需要能够编写
diff 5 3
diff time1 time2
,这些表达式的类型应该从操作数中推导出来

我知道我可以为
Diff-Int
Diff-Double-Double
Diff-Rational
分别定义实例,但这不是一个理想的解决方案,因为可以定义
Num
的新实例,并且代码必须处理它们,而不必为每个实例定义另一个
Diff

下面是一个最简单的完整示例:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}

module Example where

class Diff a b c | a b -> c where
  diff :: a -> b -> c

instance Num a => Diff a a a where
  diff = (-)

data TimePoint a = TP a deriving Show
data TimeInterval a = TI a deriving Show

instance Num a => Diff (TimePoint a) (TimePoint a) (TimeInterval a) where
  diff (TP x) (TP y) = TI (x - y)

问题是
Diff(timepointa)(timepointa)
恰好是
Diff a的特例。您可能会认为“这不是因为
numa
约束”,但请记住,您永远无法证明某个类型不是某个类的实例,因为该实例可能会在以后添加


解决方案是不定义
差异实例。相反,请分别定义
Diff Int
Diff Double
Diff Rational

您可以尝试一种常见的技巧来避免答案中描述的@leftaround头部匹配问题

instance {-# OVERLAPPABLE #-} (a ~ b, a ~ c, Num a) => Diff a b c where
    diff = (-)

这需要
不可判定实例
类型族
来启用统一约束,并且除非
差异
的结果最终被具体类型化,否则将不起作用,因此某种程度的推断(例如在GHCi中)是不可能的。

在实践中,您可能只需要使用我在仿射空间中看到的.Hmm,这相当于为每个数值类定义一个单独的实例。为了重申我对dupe的立场:你声称“我需要能够编写
diff 5 3
diff time1 time2
”,但不要激发这种需要。“我对此表示怀疑。”丹尼尔瓦格纳,这个所谓的傻瓜,甚至还没有接近。在我的真实代码中,
diff
被称为
(-
)。其动机是消除“范围和限制/TODO”第二个要点中解释的限制。@n.m.dupe提出的问题并不接近。但答案是:你别无选择,只能创建一堆实例或使用一个新类型的包装器。阅读这里的答案,你会发现它们是一样的。而对于后代来说,我选择的欺骗是。我仍然不同意投票重新开放的人的意见:那里的答案也回答了这个问题。*分别定义Diff Int Int和Diff Double Double和Diff Rational“我知道这会起作用,但这并不理想,因为我所处的环境可能会定义Num的新实例。我将把这一点添加到问题中。如果这是一个要求,那么您最好找到一个根本不需要自定义类的解决方案。这不是一个要求,但并不可取。如果我能为Num的每个实例自动生成一个单独的Diff实例就好了……我想说的是,只要为这些类型中的每一个写出一行实例就行了。好吧,你可以用重叠的实例来代替。我个人不喜欢这些,但有些人成功地使用了它们。谢谢,这似乎大部分都是有效的,尽管
diff 5 3
由于文本过载仍然不起作用。
UndededicatableInstances
不会影响其他模块吗?我不确定-我在一些地方()看到过对它的描述,但这只是操纵实例选择器的一种方式。
instance {-# OVERLAPPABLE #-} (a ~ b, a ~ c, Num a) => Diff a b c where
    diff = (-)