Клас 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, ())
Клас типу та його закони можна рівнозначно записати так:
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
Можна поцікавитись, як може виглядати фуніктор, який є моноідальним оксалом щодо тієї самої структури:
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
Якщо ми подумаємо про типи, що беруть участь у визначеннях та законах, виявляється невтішна істина; OpApplicative
не є більш конкретним обмеженням, ніж 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
Перший закон вище тривіальний, оскільки єдиним мешканцем типу () -> ()
є функція ідентичності на ()
.
Однак три інші закони, а отже, і сам підклас, не є тривіальними. Зокрема, не кожен Applicative
є законним екземпляром цього класу.
Ось кілька Applicative
функторів, для яких ми можемо оголосити законні випадки StrongApplicative
:
Identity
VoidF
(->) r
(див. відповіді)Monoid m => (,) m
Vec (n :: Nat)
Stream
(нескінченно)
Ось декілька Applicative
s, для яких ми не можемо:
[]
Either e
Maybe
NonEmptyList
Зображення тут говорить про те, що StrongApplicative
клас є в певному сенсі FixedSize
класом, де "фіксований розмір" * означає, що множина ** жителів a
у мешканця f a
є фіксованою.
Це можна сказати як дві здогадки:
- Кожен, що
Applicative
представляє контейнер «фіксованого розміру» елементів його аргументу типу, є примірникомStrongApplicative
- Не
StrongApplicative
існує жодного примірника , в якому кількість подійa
може змінюватися
Чи може хтось подумати про контрприклади, які спростовують ці здогадки, чи якісь переконливі міркування, які демонструють, чому вони правдиві чи неправдиві?
* Я розумію, що я неправильно визначив прикметник "фіксованого розміру". На жаль, завдання трохи кругле. Я не знаю жодного формального опису контейнера "фіксованого розміру", і я намагаюся придумати його. StrongApplicative
- моя найкраща спроба поки що.
Щоб оцінити, чи це добре визначення, мені потрібно щось порівняти. З огляду на деяке формальне / неофіційне визначення того, що означає функтору мати заданий розмір або кратність щодо жителів аргументу його типу, питання полягає в тому, чи існування StrongApplicative
екземпляра точно відрізняє функціонери фіксованого та різного розміру.
Не усвідомлюючи існуючого формального визначення, я звертаюся до інтуїції, використовуючи термін "фіксований розмір". Однак якщо хтось уже знає про існуючий формалізм за розміром функтора і може порівняти StrongApplicative
його, тим краще.
** Під "кратністю" я маю на увазі у розрізненому розумінні "скільки" довільних елементів типу параметра функтора, що зустрічаються у мешканця кодомінного типу функтора. Це не стосується конкретного типу, до якого застосовується функтор, а отже, не враховуючи конкретних мешканців типу параметра.
Недокладність цього викликала деяку плутанину в коментарях, тож ось декілька прикладів того, що я вважаю б розміром / кратністю різних функторів:
VoidF
: фіксовано, 0Identity
: фіксований, 1Maybe
: змінна, мінімум 0, максимум 1[]
: змінна, мінімум 0, максимально нескінченнаNonEmptyList
: змінна, мінімум 1, максимально нескінченнаStream
: нерухомий, нескінченнийMonoid m => (,) m
: фіксований, 1data Pair a = Pair a a
: фіксований, 2Either x
: змінна, мінімум 0, максимум 1data Strange a = L a | R a
: фіксований, 1
(->) r
вони є ізоморфними в правильному напрямку до цього.
(->) r
; вам потрібні компоненти ізоморфізму для збереження міцної прикладної структури. Чомусь Representable
клас Хаскелла має таємничий tabulate . return = return
закон (який насправді навіть не має сенсу для немонадних функціонерів), але він дає нам 1/4 умов, про які нам потрібно сказати, tabulate
і zip
це морфізми відповідної категорії моноїдів . Інші 3 - це додаткові закони, які потрібно вимагати.
tabulate
і index
це морфізми відповідної категорії ..."
return
не є серйозною проблемою. cotraverse getConst . Const
є реалізацією за замовчуванням для return
/ pure
з точки зору Distributive
і, оскільки дистрибутори / представники мають фіксовану форму, ця реалізація є унікальною.