为了简单起见,我将使用这个人为的示例类(重点是我们从方法中获得了一些昂贵的数据):
class HasNumber a where
getNumber :: a -> Integer
getFactors :: a -> [Integer]
getFactors a = factor . getNumber
当然,我们可以记住这个类的实现,例如:
data Foo = Foo {
fooName :: String,
fooNumber :: Integer,
fooFactors :: [Integer]
}
foo :: String -> Integer -> Foo
foo a n = Foo a n (factor n)
instance HasNumber Foo where
getNumber = fooNumber
getFactors = fooFactors
但是要求将'因素'字段手动添加到任何将成为HasNumber
实例的记录中似乎有点难看。下一个想法:
data WithFactorMemo a = WithFactorMemo {
unWfm :: a,
wfmFactors :: [Integer]
}
withFactorMemo :: HasNumber a => a -> WithFactorMemo a
withFactorMemo a = WithFactorMemo a (getFactors a)
instance HasNumber a => HasNumber (WithFactorMemo a) where
getNumber = getNumber . unWfm
getFactors = wfmFactors
但是,这需要大量的样板来将原始a
的所有其他操作解除为WithFactorMemo a
。
有没有优雅的解决方案?
答案 0 :(得分:7)
这是解决方案:丢失类型类。我已经谈过这个here和here。任何类别TC a
,其每个成员都将一个a
作为参数,与数据类型是同构的。这意味着您的HasNumber
类的每个实例都可以在此数据类型中表示:
data Number = Number {
getNumber' :: Integer,
getFactors' :: [Integer]
}
即,通过这种转变:
toNumber :: (HasNumber a) => a -> Number
toNumber x = Number (getNumber x) (getFactors x)
Number
显然也是HasNumber
的一个实例。
instance HasNumber Number where
getNumber = getNumber'
getFactors = getFactors'
这个同构现象告诉我们这个类是伪装的数据类型,它应该死掉。只需使用Number
代替。最初可能不明白如何做到这一点,但有一点经验应该很快到来。例如,您的Foo
类型变为:
data Foo = Foo {
fooName :: String,
fooNumber :: Number
}
您的备忘录将免费提供,因为这些因素存储在Number
数据结构中。