La Applicativetypeclass rappresenta i lassisti monoidali che preservano la struttura monoidale cartesiana nella categoria delle funzioni tipizzate.
In altre parole, visti gli isomorfismi canonici che testimoniano che (,)forma una struttura monoidale:
-- 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, ())
La tabella dei tipi e le sue leggi possono equivalentemente essere scritte in questo modo:
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
Ci si potrebbe chiedere come potrebbe apparire un funzione che è opaca monoideale rispetto alla stessa struttura:
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
Se pensiamo ai tipi coinvolti nelle definizioni e nelle leggi, viene rivelata la verità deludente; OpApplicativenon è un vincolo più specifico di Functor:
instance Functor f => OpApplicative f
where
unzip fab = (fst <$> fab, snd <$> fab)
unhusk = const ()
Tuttavia, mentre ogni funzione Applicative(davvero, qualsiasi Functor) è banale OpApplicative, non c'è necessariamente una bella relazione tra Applicativelassità e OpApplicativeoplaxità. Quindi possiamo cercare potenti funzioni monoidali nella struttura monoidale cartesiana:
class (Applicative f, OpApplicative f) => StrongApplicative f
-- Laws:
-- unhusk . husk = id
-- husk . unhusk = id
-- zip . unzip = id
-- unzip . zip = id
La prima legge sopra è banale, poiché l'unico abitante del tipo () -> ()è la funzione di identità attiva ().
Tuttavia, le restanti tre leggi, e quindi la sottoclasse stessa, non sono banali. In particolare, non tutte Applicativesono istanze lecite di questa classe.
Ecco alcuni Applicativefunzioni per le quali possiamo dichiarare casi legittimi di StrongApplicative:
IdentityVoidF(->) r(vedi le risposte)Monoid m => (,) mVec (n :: Nat)Stream(infinito)
Ed ecco alcuni Applicatives per i quali non possiamo:
[]Either eMaybeNonEmptyList
Lo schema qui suggerisce che la StrongApplicativeclasse è in un certo senso la FixedSizeclasse, dove "dimensione fissa" * significa che la molteplicità ** degli abitanti di aun abitante di f aè fissa.
Questo può essere affermato come due congetture:
- Ogni
Applicativerappresentazione di un contenitore di "dimensioni fisse" di elementi del suo argomento type è un'istanza diStrongApplicative - Non
StrongApplicativeesiste alcuna istanza di in cui il numero di occorrenze diapuò variare
Qualcuno può pensare a controesempi che confutano queste congetture, o alcuni ragionamenti convincenti che dimostrano perché sono veri o falsi?
* Mi rendo conto di non aver definito correttamente l'aggettivo "dimensione fissa". Sfortunatamente il compito è un po 'circolare. Non conosco alcuna descrizione formale di un contenitore di "dimensioni fisse" e sto provando a trovarne uno. StrongApplicativeè il mio miglior tentativo finora.
Per valutare se questa è una buona definizione, tuttavia, ho bisogno di qualcosa con cui confrontarla. Data una definizione formale / informale di cosa significhi per un funzione avere una determinata dimensione o molteplicità rispetto agli abitanti del suo tipo di argomento, la domanda è se l'esistenza di StrongApplicativeun'istanza distingue precisamente i funzioni di dimensioni fisse e variabili.
Non essendo a conoscenza di una definizione formale esistente, sto facendo un appello all'intuizione nel mio uso del termine "dimensione fissa". Tuttavia, se qualcuno conosce già un formalismo esistente per le dimensioni di un funzione e può confrontarsi StrongApplicativecon esso, tanto meglio.
** Per "molteplicità" mi riferisco in senso lato a "quanti" elementi arbitrari del tipo di parametro del funzione si verificano in un abitante del tipo di codice del funzione. Questo è senza riguardo al tipo specifico a cui viene applicato il funzione, e quindi senza riguardo a specifici abitanti del tipo di parametro.
Non essere precisi su questo ha causato un po 'di confusione nei commenti, quindi ecco alcuni esempi di ciò che considererei la dimensione / molteplicità dei vari funzioni:
VoidF: fisso, 0Identity: fisso, 1Maybe: variabile, minimo 0, massimo 1[]: variabile, minimo 0, massimo infinitoNonEmptyList: variabile, minimo 1, massimo infinitoStream: fisso, infinitoMonoid m => (,) m: fisso, 1data Pair a = Pair a a: fisso, 2Either x: variabile, minimo 0, massimo 1data Strange a = L a | R a: fisso, 1
(->) rsono e sono isomorfi nel modo giusto.
(->) r; hai bisogno dei componenti dell'isomorfismo per preservare la forte struttura applicativa. Per qualche ragione la Representabletavola di caratteri in Haskell ha una tabulate . return = returnlegge misteriosa (che non ha nemmeno senso per i non monadici), ma ci dà 1/4 delle condizioni che dobbiamo dire tabulatee che zipsono morfismi di una categoria adatta di monoidi . Le altre 3 sono leggi extra che devi richiedere.
tabulatee indexsono morfismi di una categoria adatta ..."
returnnon è un problema serio. cotraverse getConst . Constè un'implementazione predefinita per return/ purein termini di Distributivee, poiché i distributori / rappresentanti hanno una forma fissa, tale implementazione è unica.