Ta Applicativeklisza 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; OpApplicativenie 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 Applicativefunktor (naprawdę każdy Functor) jest trywialny OpApplicative, niekoniecznie istnieje przyjemny związek między Applicativewiotkościami i OpApplicativezmę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 Applicativejest zgodną z prawem instancją tej klasy.
Oto niektóre Applicativefunktory, dla których możemy zadeklarować legalne przypadki StrongApplicative:
IdentityVoidF(->) r(Zobacz odpowiedzi)Monoid m => (,) mVec (n :: Nat)Stream(nieskończony)
A oto kilka Applicatives, dla których nie możemy:
[]Either eMaybeNonEmptyList
Wzór tutaj sugeruje, że StrongApplicativeklasa jest w pewnym sensie FixedSizeklasą, gdzie „ustalony rozmiar” * oznacza, że liczebność ** mieszkańców aw mieszkance f ajest ustalona.
Można to określić jako dwie domysły:
- Każda
Applicativereprezentująca kontener „o stałym rozmiarze” elementów argumentu typu jest instancją klasyStrongApplicative - Nie
StrongApplicativeistnieje żaden przypadek , w którym liczba wystąpieńamoż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ć. StrongApplicativeto 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 StrongApplicativeinstancji 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
(->) rsą i są izomorficzne we właściwy sposób.
(->) r; potrzebujesz składników izomorfizmu, aby zachować silną strukturę aplikacyjną. Z jakiegoś powodu Representabletyp w Haskell ma tajemnicze tabulate . return = returnprawo (które tak naprawdę nie ma sensu nawet w przypadku funktorów niemonadycznych), ale daje nam 1/4 warunków, które musimy powiedzieć, tabulatei zipsą morfizmami odpowiedniej kategorii monoidów . Pozostałe 3 to dodatkowe prawa, których musisz wymagać.
tabulatei indexsą morfizmy odpowiedniej kategorii ...”
returnnie jest poważnym problemem. cotraverse getConst . Constjest domyślną implementacją dla return/ purepod względem Distributive, a ponieważ dystrybutorzy / reprezentanci mają ustalony kształt, ta implementacja jest unikalna.