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

Mutually recursively defined typeclass methods with defaults

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

出现以下错误消息:

Conflicting definitions for bdistribute

现在,我明白了这个错误的意义所在;而且,我认为我想要的也是合理且定义明确的:如果您手写 DistributiveB 实例,您可以覆盖 bdistribute and/or bshape,但是如果你只写 instance DistributiveB MyB 那么你会得到 bshape 根据 bdistributebdistribute 定义的 gdistributeDefault.

一个妥协是放弃第一个默认定义:当用户手动实施 bshape 时,要求添加一行以获得另一个 "default" 实施并不过分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