如何定义一个我们可以添加“功能”而不会遇到重叠实例的环境?
假设我们有以下数据类型和类型类:
type Name = String
data Fruit = Orange | Pear | Apple
data Vegetable = Cucumber | Carrot | Spinach
data Legume = Lentils | Chickpeas | BlackEyedPeas
class HasFruit e where
getFruit :: e -> Name -> Maybe Fruit
class HasVegetable e where
getVegetable :: e -> Name -> Maybe Vegetable
class HasLegume e where
getLegume :: e -> Name -> Maybe Legume
现在我们想定义几个需要环境中某些成分的函数:
data Smootie
mkSmoothie :: (HasFruit e, HasVegetable e) => e -> Smootie
mkSmoothie = undefined
data Salad
mkSalad :: (HasVegetable e, HasLegume e) => e -> Salad
mkSalad = undefined
我们定义了一些实例Has*
:
instance HasFruit [Fruit] where
getFruit = undefined
instance HasVegetable [Vegetable] where
getVegetable = undefined
instance HasLegume [Legume] where
getLegume = undefined
最后,我们想定义一个准备冰沙和沙拉的函数:
cook :: (Smootie, Salad)
cook = let ingredients = undefined in
(mkSmoothie ingredients, mkSalad ingredients)
现在第一个问题是,将什么作为成分传递给上面定义的实例可以使用?我的第一个解决方案是使用元组:
instance HasFruit e0 => HasFruit (e0, e1, e2) where
getFruit (e0, _, _) = getFruit e0
instance HasVegetable e1 => HasVegetable (e0, e1, e2) where
getVegetable (_, e1, _) = getVegetable e1
instance HasLegume e2 => HasLegume (e0, e1, e2) where
getLegume (_, _, e2) = getLegume e2
cook :: (Smootie, Salad)
cook = let ingredients = ([Orange], [Cucumber], [BlackEyedPeas]) in
(mkSmoothie ingredients, mkSalad ingredients)
这虽然很麻烦,但很有效。但是现在假设我们决定添加一个mkStew
,这需要一些HasMeat
实例。然后我们必须更改上面的所有实例。此外,如果我们想单独使用mkSmothie
,我们不能直接通过([Orange], [Cucumber])
,因为没有为它定义实例。
我可以定义:
data Sum a b = Sum a b
和实例:
instance HasFruit e0 => HasFruit (Sum e0 e1) where
getFruit (Sum e0 _) = getFruit e0
instance HasVegetable e1 => HasVegetable (Sum e0 e1) where
getVegetable (Sum _ e1) = getVegetable e1
instance HasLegume e1 => HasLegume (Sum e0 e1) where
getLegume (Sum _ e1) = getLegume e1
但是以下方法不起作用(没有实例HasVegetable [Legume]
):
cook1 :: (Smootie, Salad)
cook1 = let ingredients = Sum [Orange] (Sum [Cucumber] [BlackEyedPeas]) in
(mkSmoothie ingredients, mkSalad ingredients)
而且这个实例会重叠!
instance HasVegetable e0 => HasVegetable (Sum e0 e1) where
getVegetable (Sum e0 e1) = getVegetable e0
有没有办法以优雅的方式解决这个问题?