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ódło
(->) r
są i są izomorficzne we właściwy sposób.(->) r
; potrzebujesz składników izomorfizmu, aby zachować silną strukturę aplikacyjną. Z jakiegoś powoduRepresentable
typ w Haskell ma tajemniczetabulate . 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
izip
są morfizmami odpowiedniej kategorii monoidów . Pozostałe 3 to dodatkowe prawa, których musisz wymagać.tabulate
iindex
są morfizmy odpowiedniej kategorii ...”return
nie jest poważnym problemem.cotraverse getConst . Const
jest domyślną implementacją dlareturn
/pure
pod względemDistributive
, a ponieważ dystrybutorzy / reprezentanci mają ustalony kształt, ta implementacja jest unikalna.Odpowiedzi:
Nie jestem pewien tej pierwszej przypuszczenia i na podstawie dyskusji z @AsadSaeeduddin prawdopodobnie trudno będzie ją udowodnić, ale druga hipoteza jest prawdziwa. Aby zobaczyć dlaczego, rozważ
StrongApplicative
prawohusk . unhusk == id
; to jest dla wszystkichx :: f ()
,husk (unhusk x) == x
. Ale w Haskell,unhusk == const ()
tak, że prawo jest równoznaczne ze stwierdzeniem, dla wszystkichx :: f ()
,husk () == x
. Ale to z kolei oznacza, że może istnieć tylko jedna odrębna wartość typuf ()
: gdyby były dwie wartościx, y :: f ()
, wtedyx == husk ()
ihusk () == y
takx == y
. Ale jeśli istnieje tylko jedna możliwaf ()
wartość,f
musi mieć ustalony kształt. (np. dladata Pair a = Pair a a
istnieje tylko jedna wartość typuPair ()
, to jestPair () ()
, ale istnieje wiele wartości typuMaybe ()
lub[()]
.) Zatemhusk . unhusk == id
oznacza, żef
musi mieć ustalony kształt.źródło
f ()
„ implikuje ”, że liczba wystąpieńa
nie może się różnić” w obecności fantazyjnych GADT i innych rzeczy?a
nie może się różnić” nie jest wystarczającym warunkiem dlaStrongApplicative
wystąpienia; na przykładdata Writer w a = Writer (w,a)
ma niezmienną wielokrotnośća
, ale nie jestStrongApplicative
. W rzeczywistości potrzebujesz kształtu funktora, aby był niezmienny, co, jak sądzę, jest konsekwencjąf ()
bycia singletonem.f ()
” implikuje „liczba wystąpieńa
nie może się różnić”. Nie zgadzam się z tym, że ostatni krok tego argumentu nie jest wyraźnie prawdziwy; npdata Weird a where One :: a -> Weird a; None :: Weird Bool
. rozważ . Istnieje wyraźna wartość typuWeird ()
, ale różni konstruktorzy mają różną liczbęa
s wewnątrz. (Nie jest to pełny kontrprzykład, ponieważFunctor
jest trudny, ale skąd wiemy, że nie można tego naprawić?)Weird ()
jest singletonem, ale nie ma ustalonego kształtu. AleWeird
nie jestFunctor
, więc i tak nie może byćStrongApplicative
. Przypuszczam, że właściwym przypuszczeniem byłoby: jeślif
jestFunctor
, to czyf ()
bycie singletonem implikuje, żef
ma ustalony kształt ? Podejrzewam, że to prawda, ale jak zauważyłeś, nie mam jeszcze żadnego dowodu.Możemy odpowiedzieć przynajmniej na jedno z poniższych pytań przecząco:
W rzeczywistości jeden z przykładów zgodnych z prawem
StrongApplicative
w pierwotnym pytaniu jest błędny. Aplikacja pisarzaMonoid => (,) m
nie jestStrongApplicative
, ponieważ na przykładhusk $ unhusk $ ("foo", ()) == ("", ()) /= ("foo", ())
.Podobnie przykład kontenera o stałym rozmiarze:
o stałej krotności 1, nie jest silną aplikacją, ponieważ jeśli zdefiniujemy
husk = Left
to,husk $ unhusk $ Right () /= Right ()
, i vice versa. Równoważnym sposobem na zobaczenie tego jest to, że jest to po prostu aplikacja pisarska do wyboru monoiduBool
.Istnieją więc aplikacje o ustalonym rozmiarze, które nie są
StrongApplicative
. Nie wiadomo, czy wszystkieStrongApplicative
mają stały rozmiar.źródło
Weźmy reprezentatywne funktory jako naszą definicję „kontenera o stałym rozmiarze”:
Rzeczywistość
Representable
ma kilka praw i nadklas, ale do celów tej odpowiedzi potrzebujemy tylko dwóch właściwości:(Okej, potrzebujemy również prawnie przestrzeganych przepisów
instance StrongApplicative ((->) r)
. Spokojny, już zgadzasz się, że istnieje).Jeśli weźmiemy tę definicję, mogę potwierdzić tę hipotezę 1:
jest prawdziwy. Oto jak:
Jest wiele praw do udowodnienia, ale skupię się tylko na Wielkiej Czwórce, która
StrongApplicative
dodaje - prawdopodobnie już wierzysz w wprowadzenieApplicative
iOpApplicative
, ale jeśli nie, ich dowody wyglądają tak jak te poniżej ( które z kolei wyglądają bardzo podobnie do siebie). Dla jasności, użyjęzipf
,huskf
itp na przykład funkcji, azipr
,huskr
itp dla przedstawialnym instancji, dzięki czemu można śledzić, który jest który. (I aby łatwo było zweryfikować, że nie bierzemy tego, co próbujemy udowodnić, jako założenie! Można to wykorzystaćunhuskf . huskf = id
podczas dowodzeniaunhuskr . huskr = id
, ale błędne byłoby założenie, żeunhuskr . huskr = id
tego samego dowodu.)Potwierdzenie każdego prawa przebiega w zasadzie w ten sam sposób: rozwinąć definicje, porzucić izomorfizm, który
Representable
daje, a następnie zastosować analogiczne prawo dla funkcji.źródło
instance StrongApplicative f => Representable f where type Rep f = forall x. f x -> x
.index
jest proste. Do tej pory nie wypracowałem tej sztuczkitabulate
, ale wydaje się kusząco blisko.StrongApplicative
instancję, ale nie mogłem udowodnić praw. Gratulacje za zrozumienie! Próbowałem zrobić toRepresentable
samo, co podaneStrongApplicative
, ale nie mogłem wymyślić dobregoRep
typu - byłbym ciekawy, jak toforall x. f x -> x
osiągnąć?forall x. f x -> x
są dokładnie te funkcje, które wybierają otwór i zwracają wartość w tym otworze. (I zastanawiając się, jak wdrożyćtabulate
, wpadłem na sprzeciw wobec tego typuunhusk
; zobacz szczegóły na temat samego pytania).forall x. f x -> x
zadziała jakRep
. Moje rozumowanie jest takie, że używając tegoRep
możesz pisaćindex
dla dowolnego typu, nie tylko dlaStrongApplicative
tych - więc podejrzewam, żeforall x. f x -> x
może to być zbyt ogólne.