为异构操作创建类型类的问题

时间:2018-09-20 22:11:39

标签: 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 -- 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 λ

因此,我必须在编译器类型应该是“显而易见的”的地方写类型签名。

让我们尝试一些帮助。

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

它现在应该能够确定结果的类型!不幸的是,这无法编译:

[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 λ 

顺便说一句,我也尝试过使用关联类型的家庭来代替肱二头肌,其结果可预测地相似。

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

我知道我可以为Diff Int Int IntDiff Double Double DoubleDiff Rational Rational 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)

2 个答案:

答案 0 :(得分:1)

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

解决方案是不定义Diff a a a实例。而是分别定义Diff Int Int IntDiff Double Double DoubleDiff Rational Rational Rational

答案 1 :(得分:1)

您可以尝试使用常见的技巧来避免@leftaroundabout在其答案中描述的头部匹配问题

authorizedPost() {
        getItem('token', this.setToken.bind(this)).then(
            ()=>this.post()
        );
    }

这需要instance {-# OVERLAPPABLE #-} (a ~ b, a ~ c, Num a) => Diff a b c where diff = (-) UndecidableInstances来启用统一约束,除非最终具体键入TypeFamilies的结果,否则它将无法工作,因此需要进行某种程度的推断,例如在GHCi中是不可能的。