具有默认值的相互递归定义的类型类方法



我想定义一个有两个方法的类型类,其中实现任何一个方法都足够了(但如果需要,可以独立实现这两个方法(。这种情况与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

最新更新