我想定义一个有两个方法的类型类,其中实现任何一个方法都足够了(但如果需要,可以独立实现这两个方法(。这种情况与Eq
中的情况相同,其中x == y = not (x /= y)
和x /= y = not (x == y)
。到目前为止还不错,我可以做完全相同的事情:
class (FunctorB b) => DistributiveB (b :: (Type -> Type) -> Type) where
bdistribute :: (Distributive f) => f (b g) -> b (Compose f g)
bdistribute x = bmap (f -> Compose $ fmap f . bsequence' <$> x) bshape
bshape :: b ((->) (b Identity))
bshape = bdistribute' id
bdistribute' :: (DistributiveB b, Distributive f) => f (b Identity) -> b f
bdistribute' = bmap (fmap runIdentity . getCompose) . bdistribute
然而,我也想提供bdistribute
的通用默认实现,如果bdistribute
没有定义,我可以这样做:
class (FunctorB b) => DistributiveB (b :: (Type -> Type) -> Type) where
bdistribute :: (Distributive f) => f (b g) -> b (Compose f g)
default bdistribute
:: forall f g
. CanDeriveDistributiveB b f g
=> (Distributive f) => f (b g) -> b (Compose f g)
bdistribute = gbdistributeDefault
bshape :: b ((->) (b Identity))
bshape = bdistribute' id
然而,一旦我想同时做这两件事,它就会崩溃:
class (FunctorB b) => DistributiveB (b :: (Type -> Type) -> Type) where
bdistribute :: (Distributive f) => f (b g) -> b (Compose f g)
bdistribute x = bmap (f -> Compose $ fmap f . bsequence' <$> x) bshape
default bdistribute
:: forall f g
. CanDeriveDistributiveB b f g
=> (Distributive f) => f (b g) -> b (Compose f g)
bdistribute = gbdistributeDefault
bshape :: b ((->) (b Identity))
bshape = bdistribute' id
带有以下错误消息:
bdistribute
的冲突定义
现在,我可以理解这个错误的意义;但是,我认为我想要的也是合理和明确的:如果你手工编写你的DistributiveB
实例,你可以覆盖bdistribute
和/或bshape
,但如果你只写instance DistributiveB MyB
,那么你会得到根据bdistribute
定义的bshape
和从gdistributeDefault
定义的bdistribute
。
一个折衷方案是放弃第一个默认定义:当用户手动实现bshape
时,要求添加一行以从中获得bdistribute
的另一个"默认"实现并不过分。
class FunctorB b => DistributiveB b where
bdistribute :: Distributive f => f (b g) -> b (Compose f g)
default bdistribute :: CanDeriveDistributiveB b f g => ...
bdistribute = ...
bshape :: b ((->) (b Identity))
bshape = ...
-- Default implementation of bdistribute with an explicitly defined bshape
bdistributeDefault :: DistributiveB b => f (b g) -> b (Compose f g)
bdistributeDefault x = bmap (f -> Compose $ fmap f . bsequence' <$> x) bshape
所以实例看起来是这样的:
-- Generic default
instance DistributiveB MyB
-- Manual bshape
instance DistributiveB MyB where
distribute = distributeDefault -- one extra line of boilerplate
bshape = ... -- custom definition
-- Manual distribute
instance DistributiveB MyB where
distribute = ...
-- bshape has a default implementation