假设我想创建一个可以接受不同类型操作数的泛型差分操作。
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)
一切都很好。除非我试着测试我的
diff
在GHCi中,我得到:
*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 λ
顺便说一句,我也尝试过用关联型家族来代替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)