La Applicative
typeclass 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; OpApplicative
non è 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 Applicative
lassità e OpApplicative
oplaxità. 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 Applicative
sono istanze lecite di questa classe.
Ecco alcuni Applicative
funzioni per le quali possiamo dichiarare casi legittimi di StrongApplicative
:
Identity
VoidF
(->) r
(vedi le risposte)Monoid m => (,) m
Vec (n :: Nat)
Stream
(infinito)
Ed ecco alcuni Applicative
s per i quali non possiamo:
[]
Either e
Maybe
NonEmptyList
Lo schema qui suggerisce che la StrongApplicative
classe è in un certo senso la FixedSize
classe, dove "dimensione fissa" * significa che la molteplicità ** degli abitanti di a
un abitante di f a
è fissa.
Questo può essere affermato come due congetture:
- Ogni
Applicative
rappresentazione di un contenitore di "dimensioni fisse" di elementi del suo argomento type è un'istanza diStrongApplicative
- Non
StrongApplicative
esiste alcuna istanza di in cui il numero di occorrenze dia
può 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 StrongApplicative
un'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 StrongApplicative
con 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
(->) r
sono e sono isomorfi nel modo giusto.
(->) r
; hai bisogno dei componenti dell'isomorfismo per preservare la forte struttura applicativa. Per qualche ragione la Representable
tavola di caratteri in Haskell ha una tabulate . return = return
legge misteriosa (che non ha nemmeno senso per i non monadici), ma ci dà 1/4 delle condizioni che dobbiamo dire tabulate
e che zip
sono morfismi di una categoria adatta di monoidi . Le altre 3 sono leggi extra che devi richiedere.
tabulate
e index
sono morfismi di una categoria adatta ..."
return
non è un problema serio. cotraverse getConst . Const
è un'implementazione predefinita per return
/ pure
in termini di Distributive
e, poiché i distributori / rappresentanti hanno una forma fissa, tale implementazione è unica.