Positività rigorosa


10

Da questo riferimento: positività rigorosa

La rigorosa condizione di positività esclude dichiarazioni come

data Bad : Set where
 bad : (Bad → Bad) → Bad
         A      B       C
 -- A is in a negative position, B and C are OK

Perché A è negativo? Anche perché B è consentito? Capisco perché C è permesso.


1
Non sono sicuro del motivo per cui questo è chiamato "negativo", ma è più comunemente noto dall'errore che produce: overflow dello stack :) Questo codice può causare l'espansione infinita Aed eventualmente esplodere lo stack (in linguaggi basati su stack).
wvxvw,

quella parte capisco che potresti scrivere cose arbitrarie e quindi il calcolo non sarà terminato. grazie
Pushpa

1
Penso che sarebbe una buona cosa menzionare la non terminazione nel corpo della tua domanda. Ho aggiornato la mia risposta basandomi sul tuo commento.
Anton Trunov,

@wvxvw Non necessariamente, può funzionare per sempre senza far esplodere lo stack, a condizione che il compilatore implementi la ricorsione della coda, ad esempio il mio esempio in OCaml di seguito non esplode lo stack.
Anton Trunov

1
@AntonTrunov certo, era più un gioco di parole sul nome del sito piuttosto che un tentativo di essere precisi.
wvxvw,

Risposte:


17

Prima una spiegazione terminologica: posizioni negative e positive provengono dalla logica. Sono circa un'asimmetria nella connettivi logici: in della A comporta in modo diverso da B . Una cosa simile accade nella teoria delle categorie, dove diciamo rispettivamente contravarianza e covariante anziché negativa e positiva. In fisica parlano di quantità che si comportano "in modo covariante" e "anche in modo contraddittorio. Quindi questo è un fenomeno molto generale. Un programmatore potrebbe pensare a loro come" input "e" output ".UNBUNB

Ora su tipi di dati induttivi.

TTT

In algebra è consuetudine che un'operazione prenda un numero finito di argomenti, e nella maggior parte dei casi prende zero (costante), uno (unario) o due (binari) argomenti. È conveniente generalizzare questo per i costruttori di tipi di dati. Supponiamo che csia un costruttore per un tipo di dati T:

  • se cè una costante possiamo pensarla come una funzione unit -> T, o equivalentemente (empty -> T) -> T,
  • se cè unario possiamo considerarlo come una funzione T -> T, o equivalentemente (unit -> T) -> T,
  • se cè binario possiamo considerarlo come una funzione T -> T -> T, o in modo equivalente T * T -> To equivalente (bool -> T) -> T,
  • se volessimo un costruttore cche accetta sette argomenti, potremmo vederlo come una funzione in (seven -> T) -> Tcui sevenesiste un tipo precedentemente definito con sette elementi.
  • possiamo anche avere un costruttore cche accetta numerosamente infiniti argomenti, che sarebbe una funzione (nat -> T) -> T.

Questi esempi mostrano che dovrebbe essere la forma generale di un costruttore

c : (A -> T) -> T

dove chiamiamo l'arity di e pensiamo come un costruttore che prende argomenti -vari di tipo per produrre un elemento di .AccATT

Ecco qualcosa di molto importante: le arità devono essere definite prima di definire T, altrimenti non possiamo dire cosa dovrebbero fare i costruttori. Se qualcuno prova ad avere un costruttore

broken: (T -> T) -> T

quindi la domanda "quanti argomenti ci brokenvogliono?" non ha una buona risposta. Potresti provare a rispondere con "richiede T-molti argomenti", ma ciò non funzionerà, perché Tnon è ancora definito. Potremmo provare a uscire dal caos usando una teoria a virgola fissa per trovare un tipo Te una funzione iniettiva (T -> T) -> T, e ci riusciremmo, ma infrangeremmo anche il principio dell'induzione T. Quindi, è solo una cattiva idea provare una cosa del genere.

λvλvcB

c : B * (A -> T) -> T

In effetti, molti costruttori possono essere riscritti in questo modo, ma non tutti, abbiamo bisogno di un ulteriore passo, vale a dire che dovremmo consentire Adi dipendere da B:

c : (∑ (x : B), A x -> T) -> T

Questa è la forma finale di un costruttore per un tipo induttivo. È anche esattamente ciò che sono i tipi W. La forma è così generale che abbiamo sempre e solo bisogno di un solo costruttore c! Anzi, se ne abbiamo due

d' : (∑ (x : B'), A' x -> T) -> T
d'' : (∑ (x : B''), A'' x -> T) -> T

allora possiamo combinarli in uno

d : (∑ (x : B), A x -> T) -> T

dove

B := B' + B''
A(inl x) := A' x
A(inr x) := A'' x

A proposito, se curry la forma generale vediamo che è equivalente a

c : ∏ (x : B), ((A x -> T) -> T)

che è più vicino a ciò che le persone effettivamente scrivono negli assistenti di prova. Gli assistenti di prova ci permettono di scrivere i costruttori in modo conveniente, ma quelli sono equivalenti al modulo generale sopra (esercizio!).


1
Grazie ancora Andrej dopo il mio pranzo, questa sarà la cosa più difficile da digerire. Saluti.
Pushpa

9

La prima occorrenza di Badsi chiama 'negativo' perché rappresenta un argomento di funzione, cioè si trova a sinistra della freccia della funzione (vedi Tipi ricorsivi gratuitamente di Philip Wadler). Immagino che l'origine del termine "posizione negativa" derivi dalla nozione di contraddizione ("contra" significa opposto).

Non è consentito definire il tipo in posizione negativa perché si possono scrivere programmi non terminanti che lo utilizzano, vale a dire che una forte normalizzazione fallisce in sua presenza (ne parleremo più avanti). A proposito, questo è il motivo del nome della regola "positività rigorosa": non consentiamo posizioni negative.

Consentiamo la seconda occorrenza di Badpoiché non causa la non terminazione e vogliamo usare il tipo da definire ( Bad) ad un certo punto in un tipo di dati ricorsivo ( prima dell'ultima freccia del suo costruttore).

È importante capire che la seguente definizione non viola la rigida regola della positività.

data Good : Set where
  good : Good → Good → Good

La regola si applica solo agli argomenti del costruttore (che sono entrambi Goodin questo caso) e non al costruttore stesso (vedere anche " Programmazione certificata con tipi dipendenti " di Adam Chlipala ).

Un altro esempio che viola la rigidità positiva:

data Strange : Set where
  strange : ((Bool → Strange) → (ℕ → Strange)) → Strange
                       ^^     ^
            this Strange is   ...this arrow
            to the left of... 

Potresti anche voler controllare questa risposta sulle posizioni negative.


Altro su non terminazione ... La pagina a cui fai riferimento contiene alcune spiegazioni (insieme a un esempio in Haskell):

Le dichiarazioni non strettamente positive vengono rifiutate perché è possibile scrivere una funzione non terminante utilizzandole. Per vedere come si può scrivere una definizione di ciclo usando il tipo di dati Bad dall'alto, vedere BadInHaskell .

Ecco un esempio analogo in Ocaml, che mostra come implementare il comportamento ricorsivo senza (!) Usare direttamente la ricorsione:

type boxed_fun =
  | Box of (boxed_fun -> boxed_fun)

(* (!) in Ocaml the 'let' construct does not permit recursion;
   one have to use the 'let rec' construct to bring 
   the name of the function under definition into scope
*)
let nonTerminating (bf:boxed_fun) : boxed_fun =
  match bf with
    Box f -> f bf

let loop = nonTerminating (Box nonTerminating)

La nonTerminatingfunzione "decomprime" una funzione dal suo argomento e la porta all'argomento originale. Ciò che è importante qui è che la maggior parte dei sistemi di tipi non consentono il passaggio di funzioni a se stessi, quindi un termine come f fnon controllerà il typecheck, poiché non esiste un tipo per fsoddisfare il typechecker. Uno dei motivi per cui sono stati introdotti i sistemi di tipo è disabilitare l'auto-applicazione (vedi qui ).

Il wrapping di tipi di dati come quello che abbiamo introdotto sopra può essere utilizzato per aggirare questo blocco sulla strada dell'incoerenza.

Voglio aggiungere che i calcoli non terminanti introducono incoerenze nei sistemi logici. Nel caso di Agda e Coq il Falsetipo di dati induttivo non ha costruttori, quindi non è mai possibile costruire un termine di prova di tipo False. Ma se fossero consentiti calcoli non terminanti, si potrebbe fare ad esempio in questo modo (in Coq):

Fixpoint loop (n : nat) : False = loop n

Quindi loop 0verificherebbe il dare loop 0 : False, quindi sotto la corrispondenza Curry-Howard significherebbe che abbiamo dimostrato una proposta falsa.

Upshot : la rigida regola della positività per le definizioni induttive impedisce calcoli non terminanti che sono disastrosi per la logica.


Ora sono confuso. Dati speciali Buono: imposta dove buono: Buono → Buono →. Cercheremo di capire e tornare tra un'ora /
Pushpa

La regola non si applica al costruttore stesso, ma solo ai suoi argomenti, ovvero le frecce al livello più alto della definizione di un costruttore non contano. Ho anche aggiunto un altro esempio (indiretto) di violazione.
Anton Trunov
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.