Ta Applicative
klisza reprezentuje luźne funktory monoidalne, które zachowują kartezjańską monoidalną strukturę w kategorii typowanych funkcji.
Innymi słowy, biorąc pod uwagę obserwowane kanoniczne izomorfizmy, które (,)
tworzą strukturę monoidalną:
-- 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, ())
Typekę i jej prawa można zapisać w następujący sposób:
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
Można się zastanawiać, jak mógłby wyglądać funktor, który jest opoidem monoidalnym w odniesieniu do tej samej struktury:
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
Jeśli pomyślimy o typach zawartych w definicjach i prawach, rozczarowująca prawda zostanie ujawniona; OpApplicative
nie jest bardziej szczegółowym ograniczeniem niż Functor
:
instance Functor f => OpApplicative f
where
unzip fab = (fst <$> fab, snd <$> fab)
unhusk = const ()
Jednak chociaż każdy Applicative
funktor (naprawdę każdy Functor
) jest trywialny OpApplicative
, niekoniecznie istnieje przyjemny związek między Applicative
wiotkościami i OpApplicative
zmętnieniami. Możemy więc poszukać silnych funktorów monoidalnych w kartezjańskiej strukturze monoidalnej:
class (Applicative f, OpApplicative f) => StrongApplicative f
-- Laws:
-- unhusk . husk = id
-- husk . unhusk = id
-- zip . unzip = id
-- unzip . zip = id
Pierwsze powyższe prawo jest trywialne, ponieważ jedynym mieszkańcem tego typu () -> ()
jest funkcja tożsamości ()
.
Jednak pozostałe trzy prawa, a zatem sama podklasa, nie są trywialne. W szczególności nie każdy Applicative
jest zgodną z prawem instancją tej klasy.
Oto niektóre Applicative
funktory, dla których możemy zadeklarować legalne przypadki StrongApplicative
:
Identity
VoidF
(->) r
(Zobacz odpowiedzi)Monoid m => (,) m
Vec (n :: Nat)
Stream
(nieskończony)
A oto kilka Applicative
s, dla których nie możemy:
[]
Either e
Maybe
NonEmptyList
Wzór tutaj sugeruje, że StrongApplicative
klasa jest w pewnym sensie FixedSize
klasą, gdzie „ustalony rozmiar” * oznacza, że liczebność ** mieszkańców a
w mieszkance f a
jest ustalona.
Można to określić jako dwie domysły:
- Każda
Applicative
reprezentująca kontener „o stałym rozmiarze” elementów argumentu typu jest instancją klasyStrongApplicative
- Nie
StrongApplicative
istnieje żaden przypadek , w którym liczba wystąpieńa
może się różnić
Czy ktoś może pomyśleć o kontrprzykładach, które obalają te przypuszczenia, lub o przekonującym uzasadnieniu, które pokazuje, dlaczego są one prawdziwe lub fałszywe?
* Zdaję sobie sprawę, że nie zdefiniowałem właściwie przymiotnika „fixed size”. Niestety zadanie jest trochę okrągłe. Nie znam żadnego formalnego opisu kontenera o „stałym rozmiarze” i próbuję go wymyślić. StrongApplicative
to moja najlepsza jak dotąd próba.
Aby jednak ocenić, czy jest to dobra definicja, potrzebuję czegoś do porównania. Biorąc pod uwagę formalną / nieformalną definicję tego, co to znaczy dla funktora mieć określoną wielkość lub krotność w stosunku do mieszkańców tego rodzaju argumentu, pytanie brzmi, czy istnienie StrongApplicative
instancji dokładnie rozróżnia funktory o stałej i zmiennej wielkości.
Nie zdając sobie sprawy z istniejącej definicji formalnej, odwołuję się do intuicji, posługując się terminem „ustalony rozmiar”. Jeśli jednak ktoś już wie o istniejącym formalizmie dotyczącym wielkości funktora i może go porównać StrongApplicative
, tym lepiej.
** Przez „wielokrotność” w luźnym znaczeniu odnoszę się do „ilu” dowolnych elementów typu parametru funktora występuje u mieszkańca typu kodomenu funktora. Jest to bez względu na rodzaj specyficznego funktor jest nakładana, a więc bez uwzględniania jakichkolwiek mieszkańców konkretnych typu parametru.
Nie precyzowanie tego spowodowało pewne zamieszanie w komentarzach, więc oto kilka przykładów tego, co uważam za rozmiar / mnogość różnych funktorów:
VoidF
: naprawiono, 0Identity
: naprawiono, 1Maybe
: zmienna, minimum 0, maksimum 1[]
: zmienna, minimum 0, maksymalna nieskończonośćNonEmptyList
: zmienna, minimum 1, maksymalna nieskończonośćStream
: naprawiony, nieskończonyMonoid m => (,) m
: naprawiono, 1data Pair a = Pair a a
: naprawiono, 2Either x
: zmienna, minimum 0, maksimum 1data Strange a = L a | R a
: naprawiono, 1
(->) r
są i są izomorficzne we właściwy sposób.
(->) r
; potrzebujesz składników izomorfizmu, aby zachować silną strukturę aplikacyjną. Z jakiegoś powodu Representable
typ w Haskell ma tajemnicze tabulate . return = return
prawo (które tak naprawdę nie ma sensu nawet w przypadku funktorów niemonadycznych), ale daje nam 1/4 warunków, które musimy powiedzieć, tabulate
i zip
są morfizmami odpowiedniej kategorii monoidów . Pozostałe 3 to dodatkowe prawa, których musisz wymagać.
tabulate
i index
są morfizmy odpowiedniej kategorii ...”
return
nie jest poważnym problemem. cotraverse getConst . Const
jest domyślną implementacją dla return
/ pure
pod względem Distributive
, a ponieważ dystrybutorzy / reprezentanci mają ustalony kształt, ta implementacja jest unikalna.