Подтвердить что ты не робот

Почему нет расширения `-XDeriveApplicative`?

GHC имеет несколько полезных языков расширений для механического получения различных стандартных типов Haskell (-XDeriveFunctor, -XDeriveFoldable, -XDeriveTraversable). Похоже, что Applicative - это еще один класс, который часто необходим и часто легко получается. Для простой записи, содержащей слоты типа a, например,

data SimpleRecord a = Simple a a a

экземпляр Applicative получается тривиально,

instance Applicative SimpleRecord where
    pure x = Simple x x x
    Simple a1 b1 c1 <*> Simple a2 b2 c2 = Simple (a1 a2) (b1 b2) (c1 c2)

Даже в немного более трудном случае, когда некоторые значения a зарываются в других аппликативных функторах, например,

data MyRecord f a = MyRecord (f a) a

разумный пример легко записывается,

instance (Applicative f) => Applicative (MyRecord f) where
    pure x = MyRecord (pure x) x
    MyRecord a1 b1 <*> MyRecord a2 b2 = MyRecord (a1 <*> a2) (b1 b1)

Почему расширение -XDeriveApplicative, реализующее эти виды механических экземпляров, не существует? Даже пакеты derive и generic-derive, по-видимому, не поддерживают Applicative. Существует ли теоретическая проблема, исключающая, что эти экземпляры обычно являются действительными (помимо тех причин, которые могут также угрожать расширениям Functor, Foldable или Traversable)?

4b9b3361

Ответ 1

Существует не более одного экземпляра Functor для данного типа данных, который следует за законами-функторами. Например, map является единственной законной реализацией fmap для списков:

fmap id      == id
fmap (f . g) == fmap f . fmap g

Но может быть более одного законопослушного экземпляра Applicative, что не обязательно очевидно.

pure id <*> v              == v
pure (.) <*> u <*> v <*> w == u <*> (v <*> w)
pure f <*> pure x          == pure (f x)
u <*> pure y               == pure ($ y) <*> u

Для списков <*> может вести себя как \fs xs -> concatMap (\f -> map f xs) fs или как zipWith ($), и не ясно, какой компилятор должен выбрать.

Ответ 2

Чтобы повторить других, нет веской причины, по которой я знаю, почему у нас не может быть -XDeriveApplicative, просто мы этого не делаем. Обычно существует более одного законного экземпляра Foldable и Traversable, и у нас есть флаг для их получения. Некоторое время у нас не было никакой реальной истории для проходящих законов, но теперь у нас есть несколько. Аналогичным образом у нас все еще нет законов Foldable (но я думаю, что мы можем, см. здесь).

Среди разных "очевидных" аппликаций, таких как "вперед" и "назад" (по отношению к самому себе <*> и даже перестановочных по сравнению с несколькими a в f a, если они есть), тогда построение применяемые здесь предложения, с обходом в синтаксическом порядке, кажутся законными. Однако для рекурсивных типов, таких как списки или даже типы с несколькими конструкторами, наш выбор правильных аппликаторов расцветает интересными способами. Для listlike регулярных рекурсивных типов очевидным аппликативным выбором является, естественно, "zipLike" один раз, поскольку он естественным образом обобщает нерекурсивный случай, сопоставляя структуру со структурой. Для типов сумм с несколькими конструкторами "очевидный" выбор гораздо сложнее определить.

Мне кажется, что вполне разумный вывод аппликации будет работать, как предлагается здесь, в синтаксическом порядке по типам (включая рекурсивные) только с одним конструктором. В случаях с несколькими конструкторами отказ кажется легитимным.

С другой стороны, в то время как Складные и Траверсируемые, похоже, часто появляются в их "очевидных" формах, мне не слишком понятно, сколько раз мы хотим определить "очевидные" аппликации по сравнению с интересными. Моя интуиция подсказывает мне, что эта функция будет редко выполняться и, возможно, просто не часто бывает полезной.