Создание сопоставленных с Функтором категории - PullRequest
3 голосов
/ 24 февраля 2020

Я люблю mapped из пакета объективов.

mapped :: Prelude.Functor f => Setter (f a) (f b) a b 

Однако недавно я начал использовать Functor из пакета категорий (из здесь я буду ссылаться на Functor категории так же, как Functor и использовать Prelude.Functor в противном случае), поэтому мой mapped больше не работает.

Так что я хотел бы сделать версию mapped это может работать с Functor. В качестве освежающего напитка для всех, кому это нужно, вид подписи Functor выглядит следующим образом:

class (Category s, Category t) => Functor (s :: α -> α -> *) (t :: β -> β -> *) (f :: α -> β) where
  map :: s a b -> t (f a) (f b)

Это займет два из (->) в ванили fmap и заменит их на s и t, которые удовлетворяют Category.

Так что, если мы хотим сделать замену для mapped, нам нужно заменить соответствующие (->) универсальными c Category удовлетворяющими типами. Итак, мы имеем дело с Setter:

mapped ::
  Prelude.Functor f =>
    ( forall g. Settable g =>
      (a -> g b) -> (f a) -> g (f b)
    )

Чтобы двигаться вперед, мы посмотрим на over, так как мы хотели бы, чтобы это было:

over mapped = map

Сейчас мы рассмотрим реализацию over (распаковка ASetter):

over :: ((a -> Identity b) -> s -> Identity t) -> (a -> b) -> s -> t
over l f = runIdentity #. l (Identity #. f)

Мы можем получить немного информации из этого. Мы знаем типы runIdentity, (#.) и цель. Если мы работаем задом наперед

(#.) runIdentity (mapped (Identity #. f)) ::
  Category t => t (f a) (f b)
mapped (Identity #. f) ::
  ( Category t
  , Profunctor t
  , Coercible (f b) z
  )
    => t (f a) z

, что является хорошим доказательством того, что третий (->) должен быть заменен общей категорией c, и приличным доказательством того, что второе не должно.

mapped ::
  ( Functor s t f
  , Category s
  , Category t
  ) =>
    ( forall g. Settable g =>
      (s a (g b)) -> t (f a) (g (f b))
    )

И вот я застрял. Я чувствую, как будто я прижимаюсь к моей концептуальной поддержке здесь. Я не знаю, является ли этот тип правильным или я что-то упускаю. Даже если бы я знал тип, я не уверен, как бы я реализовал mapped или over. Я привык использовать готовые комбинаторы из комплекта линз для изготовления моих линз, и я не думаю, что они помогут мне, когда я перестану использовать Setter.

Как мне добраться оттуда до работающей mapped реализации для Functor?

1 Ответ

0 голосов
/ 24 февраля 2020

Я считаю, что тип определенно правильный. Изучая код, mapped определяется как

mapped = taintedDot . fmap . untaintedDot

, где taintedDot, untaintedDot - методы Settable. Как следует из комментария @ leftaroundabout, в этом, вероятно, и заключается трудность перевода - вам понадобится эквивалентный класс для категорий, хотя именно то, что он будет значить, мне не подходит.

Наивное предположение будет

class Settable f where
  untainted :: Category c => c (f a) a
  tainted   :: Category c => c a (f a)

, которые очень похожи на операции Monad и Comonad, так что, возможно,

class (forall c. Monad c f, forall c. Comonad c f) => Settable f where
  untainted :: Category c => c (f a) a
  untainted = counit
  tainted   :: Category c => c a (f a)
  tainted   = unit

Альтернативы ...

Тем не менее, вы могли бы обмануть - Profunctor эквивалент в Category - это просто Arrow, поскольку вы можете поднять стрелки Hask до категориальных. Если ваши Category s также Arrow s, то вы можете сделать

data Cheat c a b = Cheat { unCheat :: c a b }

instance (Arrow c) => Profunctor (Cheat c) where
  lmap f = (arr f >>>)
  rmap f = (>>> arr f)

Затем прокрасться в Cheat

mapped' ::
  ( Functor s t f
  , Arrow s
  , Arrow t
  ) =>
    ( forall g. Settable g =>
      (s a (g b)) -> t (f a) (g (f b))
    )
mapped' = unCheat . taintedDot . Cheat . map . unCheat . untaintedDot . Cheat
...