为异构操作创建类型 class 时出现问题
Problem with creating a type class for a heterogenous operation
接下来是我正在尝试做的一个非常简化的版本。
假设我想创建一个可以接受不同类型操作数的通用差分运算。
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 λ
所以我必须在类型应该为编译器 "obvious" 的地方写类型签名。
让我们试着帮助它一下。
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 λ
顺便说一句,我也尝试用关联类型族代替 fundeps,结果与预期相似。
现在我完全明白为什么会这样了。有两个实例 Diff a a a
和 Diff (TimePoint a) (TimePoint a) (TimeInterval a)
,它们不能与 fundep 共存。问题是,我该如何解决这个问题?在新类型中包装数字不是一个可行的解决方案,我需要能够编写 diff 5 3
和 diff time1 time2
,并且这些表达式的类型应该从操作数中推导出来。
我知道我可以为 Diff Int Int Int
和 Diff Double Double Double
以及 Diff 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)
问题是 Diff (TimePoint a) (TimePoint a)
恰好是 Diff a a
的特例。您可能会认为“这不是因为 Num a
约束”,但请记住,您永远无法证明一个类型 不是 某些 class 的实例,因为稍后可能仍会添加该实例。
解决方案是不定义 Diff a a a
实例。而是分别定义 Diff Int Int Int
和 Diff Double Double Double
以及 Diff Rational Rational Rational
。
您可以尝试一个常见的技巧来避免@leftaroundabout 在他们的回答中描述的头部匹配问题
instance {-# OVERLAPPABLE #-} (a ~ b, a ~ c, Num a) => Diff a b c where
diff = (-)
这需要 UndecidableInstances
和 TypeFamilies
来启用统一约束,除非 diff
的结果最终被具体类型化,否则将无法工作,因此某种程度的推理,例如在 GHCi 中,这是不可能的。
接下来是我正在尝试做的一个非常简化的版本。
假设我想创建一个可以接受不同类型操作数的通用差分运算。
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 λ
所以我必须在类型应该为编译器 "obvious" 的地方写类型签名。
让我们试着帮助它一下。
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 λ
顺便说一句,我也尝试用关联类型族代替 fundeps,结果与预期相似。
现在我完全明白为什么会这样了。有两个实例 Diff a a a
和 Diff (TimePoint a) (TimePoint a) (TimeInterval a)
,它们不能与 fundep 共存。问题是,我该如何解决这个问题?在新类型中包装数字不是一个可行的解决方案,我需要能够编写 diff 5 3
和 diff time1 time2
,并且这些表达式的类型应该从操作数中推导出来。
我知道我可以为 Diff Int Int Int
和 Diff Double Double Double
以及 Diff 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)
问题是 Diff (TimePoint a) (TimePoint a)
恰好是 Diff a a
的特例。您可能会认为“这不是因为 Num a
约束”,但请记住,您永远无法证明一个类型 不是 某些 class 的实例,因为稍后可能仍会添加该实例。
解决方案是不定义 Diff a a a
实例。而是分别定义 Diff Int Int Int
和 Diff Double Double Double
以及 Diff Rational Rational Rational
。
您可以尝试一个常见的技巧来避免@leftaroundabout 在他们的回答中描述的头部匹配问题
instance {-# OVERLAPPABLE #-} (a ~ b, a ~ c, Num a) => Diff a b c where
diff = (-)
这需要 UndecidableInstances
和 TypeFamilies
来启用统一约束,除非 diff
的结果最终被具体类型化,否则将无法工作,因此某种程度的推理,例如在 GHCi 中,这是不可能的。