Являются ли все контейнеры фиксированного размера сильными моноидальными функторами и / или наоборот? - PullRequest
9 голосов
/ 09 марта 2020

Класс типов Applicative представляет слабые моноидальные функторы, которые сохраняют декартову моноидальную структуру в категории типизированных функций.

Другими словами, учитывая канонические изоморфизмы, свидетельствующие о том, что (,) образует моноидальную структуру:

-- Implementations left to the motivated reader
assoc_fwd :: ((a, b), c) -> (a, (b, c))
assoc_bwd :: (a, (b, c)) -> ((a, b), c)

lunit_fwd :: ((), a) -> a
lunit_bwd :: a -> ((), a)

runit_fwd :: (a, ()) -> a
runit_bwd :: a -> (a, ())

Класс типов и его l aws можно эквивалентно записать так:

class Functor f => Applicative f
  where
  zip :: (f a, f b) -> f (a, b)
  husk :: () -> f ()

-- Laws:

-- assoc_fwd >>> bimap id zip >>> zip
-- =
-- bimap zip id >>> zip >>> fmap assoc_fwd

-- lunit_fwd
-- =
-- bimap husk id >>> zip >>> fmap lunit_fwd

-- runit_fwd
-- =
-- bimap id husk >>> zip >>> fmap runit_fwd

Можно задаться вопросом, что такое функтор, который является oplax моноидальным по отношению та же структура может выглядеть так:

class Functor f => OpApplicative f
  where
  unzip :: f (a, b) -> (f a, f b)
  unhusk :: f () -> ()

-- Laws:

-- assoc_bwd <<< bimap id unzip <<< unzip
-- =
-- bimap unzip id <<< unzip <<< fmap assoc_bwd

-- lunit_bwd
-- =
-- bimap unhusk id <<< unzip <<< fmap lunit_bwd

-- runit_bwd
-- =
-- bimap id unhusk <<< unzip <<< fmap runit_bwd

Если мы подумаем о типах, включенных в определения и l aws, откроется неутешительная истина; OpApplicative не более конкретно определяет c ограничение, чем Functor:

instance Functor f => OpApplicative f
  where
  unzip fab = (fst <$> fab, snd <$> fab)
  unhusk = const ()

Однако, хотя каждый функтор Applicative (на самом деле, любой Functor) тривиально OpApplicative, существует не обязательно хорошие отношения между Applicative слабостями и OpApplicative прозрачностями. Таким образом, мы можем искать сильные моноидальные функторы с декартовой моноидальной структурой:

class (Applicative f, OpApplicative f) => StrongApplicative f

-- Laws:
-- unhusk . husk = id
-- husk . unhusk = id
-- zip . unzip = id
-- unzip . zip = id

Первый закон выше тривиален, поскольку единственный житель типа () -> () является тождественной функцией на ().

Однако остальные три l aws и, следовательно, сам подкласс, не тривиальны. В частности, не каждый Applicative является законным экземпляром этого класса.

Вот некоторые Applicative функторы, для которых мы можем объявить законные экземпляры StrongApplicative:

  • Identity
  • VoidF
  • (->) r
  • Monoid m => (,) m (см. Ответы)
  • Vec (n :: Nat)
  • Stream (бесконечно)

А вот несколько Applicative с, для которых мы не можем:

  • []
  • Either e
  • Maybe
  • NonEmptyList

Схема здесь предполагает, что класс StrongApplicative в некотором смысле является классом FixedSize, где «фиксированный размер» * означает, что кратность ** жителей a у жителей f a является фиксированной.

Это можно указать как две гипотезы:

  • Каждый Applicative, представляющий контейнер "фиксированного размера" элементов своего аргумента типа, является экземпляром StrongApplicative
  • Экземпляр StrongApplicative не существует в котором число вхождений a может варьироваться

C кто-нибудь думает о контрпримерах, которые опровергают эти предположения, или о некоторых убедительных рассуждениях, которые демонстрируют, почему они истинны или ложны? К сожалению, задача немного круглая. Я не знаю ни одного формального описания контейнера «фиксированного размера», и пытаюсь придумать его. StrongApplicative - моя лучшая попытка.

Чтобы оценить, является ли это хорошим определением, мне нужно кое-что сравнить с ним. Учитывая некоторое формальное / неформальное определение того, что означает, что функтор имеет заданный размер или кратность по отношению к обитателям аргумента его типа, вопрос заключается в том, точно ли существование экземпляра StrongApplicative различает функторы фиксированного и переменного размера.

Не зная о существующем формальном определении, я обращаюсь к интуиции при использовании термина «фиксированный размер». Однако, если кто-то уже знает о существующем формализме для размера функтора и может сравнить StrongApplicative с ним, тем лучше.

** Под "множественностью" я имею в виду в широком смысле «сколько» произвольных элементов типа параметра функтора встречается у обитателя типа фоменора кодомена. Это без относительно специфицированного типа c, к которому применяется функтор, и, следовательно, без учета каких-либо специфических c обитателей типа параметра.

Не совсем точно вызвал некоторую путаницу в комментариях, поэтому вот несколько примеров того, что я бы посчитал размером / множественностью различных функторов:

  • VoidF: фиксированный, 0
  • Identity: фиксированный, 1
  • Maybe: переменный, минимум 0, максимум 1
  • []: переменная, минимальная 0, максимальная бесконечная
  • NonEmptyList: переменная, минимальная 1, максимальная бесконечная
  • Stream: фиксированная, бесконечная
  • Monoid m => (,) m: фиксированный, 1
  • data Pair a = Pair a a: фиксированный, 2
  • Either x: переменный, минимум 0, максимум 1
  • data Strange a = L a | R a: фиксированный, 1

Ответы [ 3 ]

5 голосов
/ 10 марта 2020

Давайте возьмем представимые функторы в качестве нашего определения «контейнера фиксированного размера»:

class Representable f where
    type Rep f
    tabulate :: (Rep f -> a) -> f a
    index :: f a -> Rep f -> a

Реальное Representable имеет несколько l aws и суперклассы, но для целей этого ответа мы на самом деле нужны только два свойства:

tabulate . index = id
index . tabulate = id

(Хорошо, нам также нужен законопослушный instance StrongApplicative ((->) r). Легко, peasy, вы уже согласны, что он существует.)

Если мы примем это определение тогда я могу подтвердить, что гипотеза 1:

Каждый Applicative, представляющий контейнер «фиксированного размера» элементов своего аргумента типа, является [законопослушным] экземпляром StrongApplicative

верно. Вот как это делается:

instance Representable f => Applicative f where
    zip (fa, fb) = tabulate (zip (index fa, index fb))
    husk = tabulate . husk

instance Representable f => OpApplicative f where
    unzip fab = let (fa, fb) = unzip (index fab) in (tabulate fa, tabulate fb)
    unhusk = unhusk . index

instance Representable f => StrongApplicative f

Можно доказать очень много l aws, но я сосредоточусь только на Большой четверке, которую добавлю StrongApplicative - вы, вероятно, уже верите в начальные для Applicative и OpApplicative, но если вы этого не сделаете, их доказательства выглядят так же, как приведенные ниже (которые, в свою очередь, очень похожи друг на друга). Для наглядности я буду использовать zipf, huskf, et c. для экземпляра функции и zipr, huskr, et c. для представительного экземпляра, так что вы можете отслеживать, какой есть какой. (И чтобы легко было убедиться, что мы не принимаем то, что пытаемся доказать, как допущение! Можно использовать unhuskf . huskf = id при доказательстве unhuskr . huskr = id, но было бы неправильно предполагать unhuskr . huskr = id в это же доказательство.)

Доказательство каждого закона происходит в основном одинаково: разверните определения, отбросьте изоморфизм, который дает вам Representable, затем используйте аналогичный закон для функций.

unhuskr . huskr
= { def. of unhuskr and huskr }
(unhuskf . index) . (tabulate . huskf)
= { index . tabulate = id }
unhuskf . huskf
= { unhuskf . huskf = id }
id

huskr . unhuskr
= { def. of huskr and unhuskr }
(tabulate . huskf) . (unhuskf . index)
= { huskf . unhuskf = id }
tabulate . index
= { tabulate . index = id }
id

zipr (unzipr fab)
= { def. of unzipr }
zipr (let (fa, fb) = unzipf (index fab) in (tabulate fa, tabulate fb))
= { def. of zipr }
let (fa, fb) = unzipf (index fab) in tabulate (zipf (index (tabulate fa), index (tabulate fb)))
= { index . tabulate = id }
let (fa, fb) = unzipf (index fab) in tabulate (zipf (fa, fb))
= { def. of (fa, fb) }
tabulate (zipf (unzipf (index fab)))
= { zipf . unzipf = id }
tabulate (index fab)
= { tabulate . index = id }
fab

unzipr (zipr (fa, fb))
= { def. of zipr }
unzipr (tabulate (zipf (index fa, index fb)))
= { def. of unzipr }
let (fa', fb') = unzipf (index (tabulate (zipf (index fa, index fb))))
in (tabulate fa', tabulate fb')
= { index . tabulate = id }
let (fa', fb') = unzipf (zipf (index fa, index fb))
in (tabulate fa', tabulate fb')
= { unzipf . zipf = id }
let (fa', fb') = (index fa, index fb)
in (tabulate fa', tabulate fb')
= { def. of fa' and fb' }
(tabulate (index fa), tabulate (index fb))
= { tabulate . index = id }
(fa, fb)
5 голосов
/ 09 марта 2020

Мы можем ответить хотя бы на один из этих вопросов отрицательно:

Каждый аппликатив, представляющий контейнер "фиксированного размера" элементов своего аргумента типа, является экземпляром StrongApplicative

На самом деле один из примеров законного StrongApplicative в первоначальном вопросе неверен. Аппликатор писателя Monoid => (,) m не является StrongApplicative, потому что, например, husk $ unhusk $ ("foo", ()) == ("", ()) /= ("foo", ()).

Аналогично, пример контейнера фиксированного размера:

data Strange a = L a | R a

фиксированной кратности 1, не сильный аппликатив, потому что если мы определим husk = Left, то husk $ unhusk $ Right () /= Right (), и наоборот. Эквивалентный способ увидеть это состоит в том, что это просто аппликатор писателя для вашего выбора моноида на Bool.

Так что существуют аппликативы "фиксированного размера", которые не являются StrongApplicative. Все ли StrongApplicative s имеют фиксированный размер, еще неизвестно.

4 голосов
/ 09 марта 2020
  • Каждый Applicative, представляющий контейнер "фиксированного размера" элементов своего аргумента типа, является экземпляром StrongApplicative
  • Не существует экземпляра StrongApplicative, в котором число случаев a может варьироваться

Кто-нибудь может подумать о контрпримерах, опровергающих эти предположения, или о некоторых убедительных рассуждениях, демонстрирующих, почему они истинны или ложны?

I Я не уверен насчет этой первой гипотезы, и, основываясь на обсуждениях с @AsadSaeeduddin, это, вероятно, будет трудно доказать, но вторая гипотеза верна. Чтобы понять почему, рассмотрим закон StrongApplicative husk . unhusk == id; то есть для всех x :: f (), husk (unhusk x) == x. Но в Haskell, unhusk == const (), так что закон эквивалентен высказыванию для всех x :: f (), husk () == x. Но это, в свою очередь, означает, что может существовать только одно отдельное значение типа f (): если было два значения x, y :: f (), то x == husk () и husk () == y, поэтому x == y. Но если существует только одно возможное значение f (), тогда f должно иметь фиксированную форму. (например, для data Pair a = Pair a a, есть только одно значение типа Pair (), это Pair () (), но есть несколько значений типа Maybe () или [()].) Таким образом, husk . unhusk == id подразумевает, что f должно быть фиксированной формы.

...