Взаимно рекурсивно определенные методы класса типов со значениями по умолчанию - PullRequest
1 голос
/ 05 апреля 2020

Я хотел бы определить класс типов, который имеет два метода, где достаточно реализовать любой (но вы можете реализовать оба независимо, если это необходимо). Эта ситуация такая же, как в 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

Однако я также хотел бы предоставить обобщенную c стандартную реализацию 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, тогда вы получите bshape, определенный в терминах bdistribute и bdistribute, определенный из gdistributeDefault.

1 Ответ

2 голосов
/ 06 апреля 2020

Компромисс состоит в том, чтобы отбросить первое определение по умолчанию: к тому моменту, когда пользователь вручную реализует 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
...