Tutti i contenitori di dimensioni fisse sono potenti funzioni monoidali e / o viceversa?


9

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:

  • Identity
  • VoidF
  • (->) r
  • Monoid m => (,) m (vedi le risposte)
  • Vec (n :: Nat)
  • Stream (infinito)

Ed ecco alcuni Applicatives per i quali non possiamo:

  • []
  • Either e
  • Maybe
  • NonEmptyList

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 di apuò 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, 0
  • Identity: fisso, 1
  • Maybe: variabile, minimo 0, massimo 1
  • []: variabile, minimo 0, massimo infinito
  • NonEmptyList: variabile, minimo 1, massimo infinito
  • Stream: fisso, infinito
  • Monoid m => (,) m: fisso, 1
  • data Pair a = Pair a a: fisso, 2
  • Either x: variabile, minimo 0, massimo 1
  • data Strange a = L a | R a: fisso, 1

I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Samuel Liew

Una possibile definizione di "dimensione fissa" sarebbe "rappresentabile". Tutti i funzioni rappresentabili sono forti applicativi nel senso qui descritto, perché lo (->) rsono e sono isomorfi nel modo giusto.
Daniel Wagner,

@DanielWagner Penso che tu abbia bisogno di qualcosa di più di un semplice isomorfismo per ereditare il forte applicativo (->) 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.
Assad Saeeduddin il

Siamo spiacenti, dovrebbe essere " tabulatee indexsono morfismi di una categoria adatta ..."
Asad Saeeduddin

@AsadSaeeduddin Il modo in cui la legge è dichiarata nei documenti è stranamente specifico, forse, ma risulta che 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.
Duplode il

Risposte:


4
  • 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 di apuò variare

Qualcuno può pensare a controesempi che confutano queste congetture, o alcuni ragionamenti convincenti che dimostrano perché sono veri o falsi?

Non sono sicuro di quella prima congettura, e sulla base delle discussioni con @AsadSaeeduddin è probabile che sia difficile da dimostrare, ma la seconda congettura è vera. Per capire perché, considera la StrongApplicativelegge husk . unhusk == id; che è, per tutti x :: f (), husk (unhusk x) == x. Ma in Haskell, unhusk == const ()in modo che la legge equivale a dire per tutti x :: f (), husk () == x. Ma questo a sua volta implica che può esistere solo un valore distinto di tipo f (): se ci fossero due valori x, y :: f (), allora x == husk ()e husk () == yquindi x == y. Ma se esiste un solo f ()valore possibile , fdeve avere una forma fissa. (es. per data Pair a = Pair a a, esiste un solo valore di tipo Pair (), essendo questo Pair () (), ma ci sono più valori di tipo Maybe ()o [()].) Pertantohusk . unhusk == idimplica che fdeve essere di forma fissa.


Hm. È davvero chiaro che "esiste un solo valore distinto di tipo f ()" implica "il numero di occorrenze di anon può variare" in presenza di GADT e cose fantasiose?
Daniel Wagner,

@DanielWagner Si è scoperto che "il numero di occorrenze di anon può variare" non è una condizione sufficiente per StrongApplicativeun'istanza; per esempio, data Writer w a = Writer (w,a)ha una molteplicità non variabile di a, ma non è un StrongApplicative. In realtà hai bisogno che la forma del funzione sia invariante, che credo sia una conseguenza f ()dell'essere un singleton.
Brad

Non sono sicuro di vedere quanto sia rilevante. Nella risposta, confermando la seconda congettura, si sostiene che "un forte applicativo" implica "uno distinto f ()" implica "il numero di occorrenze di anon può variare". Sto obiettando che l'ultimo passo di tale argomento non è chiaramente vero; es data Weird a where One :: a -> Weird a; None :: Weird Bool. considerare . Esiste un valore distinto di tipo Weird (), ma diversi costruttori hanno un numero variabile di as all'interno. (Non è un controesempio completo qui perché Functorè difficile, ma come facciamo a sapere che non può essere risolto?)
Daniel Wagner

@DanielWagner Un buon punto che Weird ()è un singleton ma non ha forma fissa. Ma Weirdnon è un Functor, quindi non può essere StrongApplicativecomunque. Suppongo che la congettura rilevante sarebbe: se fè a Functor, f ()essere un singleton implica che ha funa forma fissa ? Sospetto fortemente che ciò sia vero, ma come hai notato non ho ancora alcuna prova.
Brad

5

Possiamo rispondere ad almeno una di queste domande in negativo:

Ogni Applicativo che rappresenta un contenitore "a dimensione fissa" di elementi del suo argomento di tipo è un'istanza di StrongApplicative

In effetti uno degli esempi di un lecito StrongApplicativenella domanda originale è sbagliato. L'applicatore dello scrittore Monoid => (,) mnon lo è StrongApplicative, perché per esempio husk $ unhusk $ ("foo", ()) == ("", ()) /= ("foo", ()).

Allo stesso modo, l'esempio di un contenitore di dimensioni fisse:

data Strange a = L a | R a

di molteplicità fissa 1, non è un forte applicativo, perché se lo definiamo husk = Leftallora husk $ unhusk $ Right () /= Right (), e viceversa. Un modo equivalente per vederlo è che questo è solo l'applicatore per la tua scelta del monoide Bool.

Esistono quindi applicativi di "dimensioni fisse" che non lo sono StrongApplicative. StrongApplicativeResta da vedere se tutti gli s sono di dimensioni fisse.


5

Prendiamo funzioni rappresentabili come la nostra definizione di "contenitore a dimensione fissa":

class Representable f where
    type Rep f
    tabulate :: (Rep f -> a) -> f a
    index :: f a -> Rep f -> a

Il reale Representableha alcune leggi e superclassi, ma ai fini di questa risposta, in realtà abbiamo bisogno solo di due proprietà:

tabulate . index = id
index . tabulate = id

(Va bene, anche noi abbiamo bisogno di una legge rispettosa instance StrongApplicative ((->) r). Facile peasy, sei già d'accordo che esiste.)

Se prendiamo quella definizione, allora posso confermare quella congettura 1:

Ogni Applicativerappresentazione di un contenitore di "dimensioni fisse" di elementi del suo argomento tipo è un'istanza [rispettosa della legge] diStrongApplicative

è vero. Ecco come:

instance Representable f => Applicative f where
    zip (fa, fb) = tabulate (zip (index fa, index fb))
    husk = tabulate . husk

instance Representable f => OpApplicative f where
    unzip fab = let (fa, fb) = unzip (index fab) in (tabulate fa, tabulate fb)
    unhusk = unhusk . index

instance Representable f => StrongApplicative f

Ci sono molte leggi da provare, ma mi concentrerò solo sui Big Four che StrongApplicativeaggiungono: probabilmente crederai già a quelli di punta Applicativee OpApplicative, se non lo fai, le loro prove sembrano proprio quelle qui sotto ( che a loro volta si assomigliano molto). Per chiarezza, userò zipf, huskfe così via per l'istanza di funzione, e zipr, huskre così via per l'istanza rappresentabili, in modo da poter tenere traccia di quale è quale. (E così è facile verificare che non prendiamo la cosa che stiamo provando a dimostrare come un'ipotesi! Va bene usare unhuskf . huskf = idquando si dimostra unhuskr . huskr = id, ma sarebbe sbagliato assumere unhuskr . huskr = idin quella stessa prova.)

La prova di ogni legge procede sostanzialmente nello stesso modo: srotola le definizioni, elimina l'isomorfismo che Representableti dà, quindi usa la legge analoga per le funzioni.

unhuskr . huskr
= { def. of unhuskr and huskr }
(unhuskf . index) . (tabulate . huskf)
= { index . tabulate = id }
unhuskf . huskf
= { unhuskf . huskf = id }
id

huskr . unhuskr
= { def. of huskr and unhuskr }
(tabulate . huskf) . (unhuskf . index)
= { huskf . unhuskf = id }
tabulate . index
= { tabulate . index = id }
id

zipr (unzipr fab)
= { def. of unzipr }
zipr (let (fa, fb) = unzipf (index fab) in (tabulate fa, tabulate fb))
= { def. of zipr }
let (fa, fb) = unzipf (index fab) in tabulate (zipf (index (tabulate fa), index (tabulate fb)))
= { index . tabulate = id }
let (fa, fb) = unzipf (index fab) in tabulate (zipf (fa, fb))
= { def. of (fa, fb) }
tabulate (zipf (unzipf (index fab)))
= { zipf . unzipf = id }
tabulate (index fab)
= { tabulate . index = id }
fab

unzipr (zipr (fa, fb))
= { def. of zipr }
unzipr (tabulate (zipf (index fa, index fb)))
= { def. of unzipr }
let (fa', fb') = unzipf (index (tabulate (zipf (index fa, index fb))))
in (tabulate fa', tabulate fb')
= { index . tabulate = id }
let (fa', fb') = unzipf (zipf (index fa, index fb))
in (tabulate fa', tabulate fb')
= { unzipf . zipf = id }
let (fa', fb') = (index fa, index fb)
in (tabulate fa', tabulate fb')
= { def. of fa' and fb' }
(tabulate (index fa), tabulate (index fb))
= { tabulate . index = id }
(fa, fb)

Attualmente riflettere: instance StrongApplicative f => Representable f where type Rep f = forall x. f x -> x. indexè facile. Non ho ancora risolto il trucco tabulate, ma sembra incredibilmente vicino.
Daniel Wagner,

In discussione con @AsadSaeeduddin, sono riuscito a trovare anche questa stessa StrongApplicativeistanza, ma non sono riuscito a dimostrare le leggi. Congratulazioni per averlo capito! Ho provato a fare anche l' Representableistanza StrongApplicative, ma non riuscivo a pensare a un buon Reptipo: sarei curioso di sapere, come ci riesci forall x. f x -> x?
Brad

@bradrn Ricordiamo che l'ipotesi è che queste cose abbiano una serie fissa di "buchi" in cui gli elementi si incastrano. Quindi le funzioni di tipo forall x. f x -> xsono esattamente quelle funzioni che scelgono un foro e restituiscono il valore in quel foro. (E, mentre penso a come implementare tabulate, ho escogitato un'obiezione al tipo di unhusk; vedere i commenti sulla domanda stessa per i dettagli.)
Daniel Wagner

Grazie @DanielWagner! È un approccio davvero intelligente - non ci avrei pensato.
Brad

Dopo aver provato a implementarlo, non credo di essere convinto che forall x. f x -> xfunzionerà come Rep. Il mio ragionamento è che, usando questo Rep, puoi scrivere indexper qualsiasi tipo, non solo per StrongApplicativequelli - quindi sospetto che forall x. f x -> xpotrebbe essere troppo generale.
Brad
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.