Che cos'è un tipo esistenziale?


171

Ho letto l'articolo di Wikipedia Tipi esistenziali . Ho raccolto che si chiamano tipi esistenziali a causa dell'operatore esistenziale (∃). Non sono sicuro di quale sia il punto, però. Qual è la differenza tra

T = ∃X { X a; int f(X); }

e

T = ∀x { X a; int f(X); }

?


8
Questo potrebbe essere un buon argomento per programmers.stackexchange.com. programmers.stackexchange.com
jpierson il

Risposte:


192

Quando qualcuno definisce un tipo universale ∀X, sta dicendo: Puoi collegare qualsiasi tipo tu voglia, non ho bisogno di sapere nulla del tipo per fare il mio lavoro, mi riferirò solo opacamenteX .

Quando qualcuno definisce un tipo esistenziale ∃Xstanno dicendo: userò qualunque tipo che voglio qui; non saprai nulla del tipo, quindi puoi solo riferirti ad esso comeX .

I tipi universali ti consentono di scrivere cose come:

void copy<T>(List<T> source, List<T> dest) {
   ...
}

La copyfunzione non ha idea di cosa Tsarà effettivamente, ma non è necessario.

I tipi esistenziali ti consentono di scrivere cose come:

interface VirtualMachine<B> {
   B compile(String source);
   void run(B bytecode);
}

// Now, if you had a list of VMs you wanted to run on the same input:
void runAllCompilers(List<∃B:VirtualMachine<B>> vms, String source) {
   for (∃B:VirtualMachine<B> vm : vms) {
      B bytecode = vm.compile(source);
      vm.run(bytecode);
   }
}

Ogni implementazione di una macchina virtuale nell'elenco può avere un diverso tipo di bytecode. La runAllCompilersfunzione non ha idea di quale sia il tipo di bytecode, ma non è necessario; non fa altro che inoltrare il bytecode da VirtualMachine.compilea VirtualMachine.run.

I caratteri jolly di tipo Java (es:) List<?>sono una forma molto limitata di tipi esistenziali.

Aggiornamento: ho dimenticato di menzionare che è possibile simulare tipi esistenziali con tipi universali. Innanzitutto, avvolgere il tipo universale per nascondere il parametro type. In secondo luogo, inverti il ​​controllo (ciò scambia effettivamente la parte "tu" e "I" nelle definizioni sopra, che è la differenza principale tra esistenziali e universali).

// A wrapper that hides the type parameter 'B'
interface VMWrapper {
   void unwrap(VMHandler handler);
}

// A callback (control inversion)
interface VMHandler {
   <B> void handle(VirtualMachine<B> vm);
}

Ora possiamo avere la VMWrappernostra chiamata VMHandlerche ha una handlefunzione di tipo universale . L'effetto netto è lo stesso, il nostro codice deve essere considerato Bopaco.

void runWithAll(List<VMWrapper> vms, final String input)
{
   for (VMWrapper vm : vms) {
      vm.unwrap(new VMHandler() {
         public <B> void handle(VirtualMachine<B> vm) {
            B bytecode = vm.compile(input);
            vm.run(bytecode);
         }
      });
   }
}

Un'implementazione della VM di esempio:

class MyVM implements VirtualMachine<byte[]>, VMWrapper {
   public byte[] compile(String input) {
      return null; // TODO: somehow compile the input
   }
   public void run(byte[] bytecode) {
      // TODO: Somehow evaluate 'bytecode'
   }
   public void unwrap(VMHandler handler) {
      handler.handle(this);
   }
}

12
@Kannan, +1 per una risposta molto utile, ma un po 'difficile da comprendere: 1. Penso che sarebbe di aiuto se tu potessi essere più esplicito sulla doppia natura dei tipi esistenziali e universali. Mi sono reso conto solo per caso di come tu abbia formulato i primi due paragrafi in modo molto simile; solo in seguito affermi esplicitamente che entrambe le definizioni sono sostanzialmente le stesse, ma con "I" e "tu" invertiti. Inoltre, non ho capito immediatamente a cosa dovrei fare riferimento "io" e "tu".
stakx - non contribuisce più al

2
(continua :) 2. Non capisco perfettamente il significato della notazione matematica in List<∃B:VirtualMachine<B>> vmso for (∃B:VirtualMachine<B> vm : vms). (Dato che si tratta di tipi generici, non avresti potuto usare i ?caratteri jolly di Java anziché la sintassi "self-made"?) Penso che potrebbe essere utile avere un esempio di codice in cui non ∃B:VirtualMachine<B>sono coinvolti tipi generici come quelli , ma invece un "diritto" ∃B, poiché i tipi generici sono facilmente associati ai tipi universali dopo i primi esempi di codice.
stakx - non contribuisce più al

2
Ero ∃Besplicito su dove sta avvenendo la quantificazione. Con la sintassi jolly è implicito il quantificatore (in List<List<?>>realtà significa ∃T:List<List<T>>e non List<∃T:List<T>>). Inoltre, la quantificazione esplicita consente di fare riferimento al tipo (ho modificato l'esempio per trarne vantaggio memorizzando il bytecode di tipo Bin una variabile temporanea).
Kannan Goundan,

2
La notazione matematica usata qui è sciatta come l'inferno, ma non penso che sia colpa del risponditore (è standard). Tuttavia, è meglio non abusare dei quantificatori esistenziali e universali in un modo forse ...
Noldorin,

2
@Kannan_Goundan, vorrei sapere cosa ti fa dire che i jolly Java sono una versione molto limitata di questo. Sai che potresti implementare la tua prima funzione di esempio runAllCompilers in Java puro (con una funzione di supporto per recuperare (dare un nome a) il parametro wilcard)?
LP_

107

Un valore di un tipo esistenziale come ∃x. F(x) è una coppia che contiene un tipo x e un valore del tipo F(x). Considerando che un valore di un tipo polimorfico simile ∀x. F(x)è una funzione che accetta un certo tipo xe produce un valore di tipo F(x). In entrambi i casi, il tipo si chiude su un tipo di costruttore F.

Nota che questa vista mescola tipi e valori. La prova esistenziale è un tipo e un valore. La prova universale è un'intera famiglia di valori indicizzati per tipo (o una mappatura da tipi a valori).

Quindi la differenza tra i due tipi specificati è la seguente:

T = ∃X { X a; int f(X); }

Ciò significa: un valore di tipo Tcontiene un tipo chiamato X, un valore a:Xe una funzione f:X->int. Un produttore di valori di tipo Tpuò scegliere qualsiasi tipo per Xe un consumatore non può sapere nulla X. Tranne il fatto che esiste un esempio della sua chiamata ae che questo valore può essere trasformato in un intdando a f. In altre parole, un valore di tipo Tsa come produrre in intqualche modo. Bene, potremmo eliminare il tipo intermedio Xe dire semplicemente:

T = int

Quello universalmente quantificato è un po 'diverso.

T = ∀X { X a; int f(X); }

Ciò significa: un valore di tipo Tpuò essere assegnato a qualsiasi tipo Xe produrrà un valore a:Xe una funzione f:X->int qualunque Xsia . In altre parole: un consumatore di valori di tipo Tpuò scegliere qualsiasi tipo per X. E un produttore di valori di tipo Tnon può sapere nulla X, ma deve essere in grado di produrre un valore aper qualsiasi scelta Xe di poter trasformare tale valore in un int.

Ovviamente implementare questo tipo è impossibile, perché non esiste un programma in grado di produrre un valore di ogni tipo immaginabile. A meno che non si consentano assurdità come nullo fondi.

Poiché un esistenziale è una coppia, un argomento esistenziale può essere convertito in uno universale tramite curry .

(∃b. F(b)) -> Int

equivale a:

∀b. (F(b) -> Int)

Il primo è un esistenziale di grado 2 . Questo porta alla seguente proprietà utile:

Ogni tipo di rango quantificato esistenzialmente n+1è un tipo di rango quantificato universalmente n.

Esiste un algoritmo standard per trasformare gli esistenziali in universali, chiamato Skolemization .


7
Potrebbe essere utile (o no) menzionare Skolemization en.wikipedia.org/wiki/Skolem_normal_form
Geoff Reedy,

34

Penso che abbia senso spiegare i tipi esistenziali insieme ai tipi universali, poiché i due concetti sono complementari, ovvero uno è "l'opposto" dell'altro.

Non posso rispondere a tutti i dettagli sui tipi esistenziali (come dare una definizione esatta, elencare tutti gli usi possibili, la loro relazione con tipi di dati astratti, ecc.) Perché semplicemente non sono abbastanza informato per quello. Dimostrerò solo (usando Java) ciò che questo articolo di HaskellWiki afferma essere l'effetto principale dei tipi esistenziali:

I tipi esistenziali possono essere utilizzati per diversi scopi. Ma quello che fanno è "nascondere" una variabile di tipo sul lato destro. Normalmente, qualsiasi variabile di tipo che appare a destra deve apparire anche a sinistra […]

Esempio di installazione:

Il seguente pseudo-codice non è del tutto valido Java, anche se sarebbe abbastanza facile risolverlo. In effetti, è esattamente quello che ho intenzione di fare in questa risposta!

class Tree<α>
{
    α       value;
    Tree<α> left;
    Tree<α> right;
}

int height(Tree<α> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

Lascia che ti spieghi brevemente questo per te. Stiamo definendo ...

  • un tipo ricorsivo Tree<α>che rappresenta un nodo in un albero binario. Ogni nodo memorizza un valuetipo di α e ha riferimenti a facoltativi lefte rightsottotitoli dello stesso tipo.

  • una funzione heightche restituisce la distanza più lontana da qualsiasi nodo foglia al nodo radice t.

Ora trasformiamo lo pseudo-codice sopra riportato heightin una corretta sintassi Java! (Continuerò a omettere un po 'di scaldabagno per brevità, come l'orientamento agli oggetti e i modificatori dell'accessibilità.) Mostrerò due possibili soluzioni.

1. Soluzione di tipo universale:

La soluzione più ovvia è semplicemente quella di rendere heightgenerico introducendo il parametro type α nella sua firma:

<α> int height(Tree<α> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

Ciò ti consentirebbe di dichiarare variabili e creare espressioni di tipo α all'interno di quella funzione, se lo desideri. Ma...

2. Soluzione di tipo esistenziale:

Se osservi il corpo del nostro metodo, noterai che non stiamo effettivamente accedendo o lavorando con nulla del tipo α ! Non ci sono espressioni che hanno quel tipo, né alcuna variabile dichiarata con quel tipo ... quindi, perché dobbiamo fare del tutto heightgenerico? Perché non possiamo semplicemente dimenticare α ? A quanto pare, possiamo:

int height(Tree<?> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

Come ho scritto all'inizio di questa risposta, i tipi esistenziali e universali sono di natura complementare / doppia. Pertanto, se la soluzione di tipo universale dovesse rendere height più generica, dovremmo aspettarci che i tipi esistenziali abbiano l'effetto opposto: renderla meno generica, ovvero nascondendo / rimuovendo il parametro di tipo α .

Di conseguenza, non è più possibile fare riferimento al tipo di t.valuein questo metodo né manipolare espressioni di quel tipo, poiché nessun identificatore è stato associato ad esso. (Il ?carattere jolly è un token speciale, non un identificatore che "cattura" un tipo.) t.valueÈ diventato effettivamente opaco; forse l'unica cosa che puoi ancora fare con esso è scrivere il tipo Object.

Sommario:

===========================================================
                     |    universally       existentially
                     |  quantified type    quantified type
---------------------+-------------------------------------
 calling method      |                  
 needs to know       |        yes                no
 the type argument   |                 
---------------------+-------------------------------------
 called method       |                  
 can use / refer to  |        yes                no  
 the type argument   |                  
=====================+=====================================

3
Buona spiegazione Non è necessario eseguire il cast di t.value su Object, è possibile fare riferimento ad esso come Object. Direi che il tipo esistenziale rende il metodo più generico a causa di ciò. L'unica cosa che puoi mai sapere su t.value è che si tratta di un oggetto mentre avresti potuto dire qualcosa di specifico su α (come α estende Serializable).
Craig P. Motlin,

1
Nel frattempo sono arrivato a credere che la mia risposta non spieghi davvero quali siano le esistenziali e sto pensando di scriverne un'altra che assomigli di più ai primi due paragrafi della risposta di Kannan Goudan, che ritengo sia più vicina alla "verità". Detto questo, @Craig: confrontare i generici con Objectè piuttosto interessante: mentre entrambi sono simili in quanto consentono di scrivere codice staticamente indipendente dal tipo, il primo (generici) non getta via quasi tutte le informazioni di tipo disponibili in raggiungere questo risultato. In questo senso particolare, i generici sono un rimedio per l' ObjectIMO.
stakx - non contribuisce più il

1
@stakx, in questo codice (da Effective Java) public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j); } private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); } , Eè un universal typee ?rappresenta un existential type?
Kevin Meredith,

Questa risposta non è corretta Il ?tipo in non int height(Tree<?> t)è ancora noto all'interno della funzione ed è ancora determinato dal chiamante perché è il chiamante che deve scegliere quale albero passare. Anche se la gente lo chiama il tipo esistenziale in Java, non lo è. Il ?segnaposto può essere utilizzato per implementare una forma di esistenziali in Java, in alcune circostanze, ma questo non è uno di questi.
Peter Hall,

15

Questi sono tutti buoni esempi, ma ho scelto di rispondere in modo leggermente diverso. Ricordiamo dalla matematica, che ∀x. P (x) significa "per tutte le x, posso dimostrare che P (x)". In altre parole, è una specie di funzione, mi dai una x e ho un metodo per dimostrarlo per te.

Nella teoria dei tipi, non stiamo parlando di prove, ma di tipi. Quindi in questo spazio intendiamo "per qualsiasi tipo X che mi dai, ti darò uno specifico tipo P". Ora, dato che non forniamo a P molte informazioni su X oltre al fatto che si tratta di un tipo, P non può farci molto, ma ci sono alcuni esempi. P può creare il tipo di "tutte le coppie dello stesso tipo": P<X> = Pair<X, X> = (X, X). Oppure possiamo creare il tipo di opzione P<X> = Option<X> = X | Nil:, dove Nil è il tipo di puntatori null. Siamo in grado di fare una lista fuori di esso: List<X> = (X, List<X>) | Nil. Si noti che l'ultimo è ricorsivo, i valori di List<X>sono o coppie in cui il primo elemento è una X e il secondo elemento è a List<X>oppure è un puntatore nullo.

Ora, in matematica ∃x. P (x) significa "Posso provare che esiste una x particolare tale che P (x) è vera". Ci possono essere molti di questi x, ma per dimostrarlo, uno è abbastanza. Un altro modo di pensarci è che deve esistere un insieme non vuoto di coppie prove-e-prove {(x, P (x))}.

Tradotto in teoria dei tipi: un tipo nella famiglia ∃X.P<X>è un tipo X e un tipo corrispondente P<X>. Nota che prima di dare X a P, (così che sapevamo tutto di X ma P molto poco) che ora è vero il contrario. P<X>non promette di fornire alcuna informazione su X, solo che ce n'è uno e che in effetti è un tipo.

Quanto è utile? Bene, P potrebbe essere un tipo che ha un modo di esporre il suo tipo interno X. Un esempio potrebbe essere un oggetto che nasconde la rappresentazione interna del suo stato X. Sebbene non abbiamo modo di manipolarlo direttamente, possiamo osservarne l'effetto frugando su P. Potrebbero esserci molte implementazioni di questo tipo, ma è possibile utilizzare tutti questi tipi indipendentemente da quale particolare sia stato scelto.


2
Hmm ma cosa guadagna la funzione sapendo che è un P<X>invece di uno P(stesso tipo di funzionalità e contenitore, diciamo, ma non sai che contiene X)?
Claudiu,

3
A rigor di termini, ∀x. P(x)non significa nulla sulla provabilità di P(x), solo la verità.
R .. GitHub FERMA AIUTANDO ICE

11

Per rispondere direttamente alla tua domanda:

Con il tipo universale, gli usi di Tdevono includere il parametro type X. Ad esempio T<String>o T<Integer>. Per il tipo esistenziale gli usi di Tnon includono quel parametro di tipo perché è sconosciuto o irrilevante - basta usare T(o in Java T<?>).

Ulteriori informazioni:

Tipi universali / astratti e tipi esistenziali sono una dualità di prospettiva tra il consumatore / cliente di un oggetto / funzione e il produttore / implementazione di esso. Quando una parte vede un tipo universale, l'altra vede un tipo esistenziale.

In Java è possibile definire una classe generica:

public class MyClass<T> {
   // T is existential in here
   T whatever; 
   public MyClass(T w) { this.whatever = w; }

   public static MyClass<?> secretMessage() { return new MyClass("bazzlebleeb"); }
}

// T is universal from out here
MyClass<String> mc1 = new MyClass("foo");
MyClass<Integer> mc2 = new MyClass(123);
MyClass<?> mc3 = MyClass.secretMessage();
  • Dal punto di vista di un client di MyClass, Tè universale perché puoi sostituire qualsiasi tipo per Tquando usi quella classe e devi conoscere il tipo effettivo di T ogni volta che usi un'istanza diMyClass
  • Dal punto di vista dei metodi di istanza in MyClasssé, Tè esistenziale perché non conosce il tipo reale diT
  • In Java, ?rappresenta il tipo esistenziale - quindi quando sei all'interno della classe, lo Tè fondamentalmente ?. Se si desidera gestire un'istanza di MyClasscon Tesistenziale, è possibile dichiarare MyClass<?>come secretMessage()nell'esempio sopra.

I tipi esistenziali sono talvolta usati per nascondere i dettagli di implementazione di qualcosa, come discusso altrove. Una versione Java di questo potrebbe apparire come:

public class ToDraw<T> {
    T obj;
    Function<Pair<T,Graphics>, Void> draw;
    ToDraw(T obj, Function<Pair<T,Graphics>, Void>
    static void draw(ToDraw<?> d, Graphics g) { d.draw.apply(new Pair(d.obj, g)); }
}

// Now you can put these in a list and draw them like so:
List<ToDraw<?>> drawList = ... ;
for(td in drawList) ToDraw.draw(td);

È un po 'complicato catturarlo correttamente perché sto fingendo di essere in una sorta di linguaggio di programmazione funzionale, che Java non è. Ma il punto qui è che stai acquisendo una sorta di stato più un elenco di funzioni che operano su quello stato e non conosci il tipo reale della parte dello stato, ma le funzioni lo fanno da quando erano già abbinate a quel tipo .

Ora, in Java tutti i tipi non primitivi non finali sono in parte esistenziali. Può sembrare strano, ma poiché una variabile dichiarata come Objectpotenzialmente potrebbe essere una sottoclasse Objectinvece, non è possibile dichiarare il tipo specifico, solo "questo tipo o una sottoclasse". Quindi, gli oggetti sono rappresentati come un po 'di stato più un elenco di funzioni che operano su quello stato - esattamente quale funzione chiamare viene determinata in fase di esecuzione dalla ricerca. Questo è molto simile all'uso di tipi esistenziali sopra in cui hai una parte di stato esistenziale e una funzione che opera su quello stato.

Nei linguaggi di programmazione tipicamente statici senza sottotipo e cast, i tipi esistenziali consentono di gestire elenchi di oggetti tipizzati in modo diverso. Un elenco di T<Int>non può contenere a T<Long>. Tuttavia, un elenco di T<?>può contenere qualsiasi variazione di T, consentendo di inserire molti diversi tipi di dati nell'elenco e convertirli tutti in un int (o fare qualsiasi operazione fornita all'interno della struttura dei dati) su richiesta.

Si può praticamente sempre convertire un record con un tipo esistenziale in un record senza usare le chiusure. Anche una chiusura è tipicamente esistenziale, in quanto le variabili libere su cui è chiusa sono nascoste al chiamante. Pertanto un linguaggio che supporta le chiusure ma non i tipi esistenziali può consentire di realizzare chiusure che condividono lo stesso stato nascosto che si sarebbe inserito nella parte esistenziale di un oggetto.


11

Un tipo esistenziale è un tipo opaco.

Pensa a un handle di file in Unix. Sai che il suo tipo è int, quindi puoi facilmente forgiarlo. Ad esempio, puoi provare a leggere dall'handle 43. Se succede che il programma ha un file aperto con questo handle specifico, leggerai da esso. Il tuo codice non deve essere dannoso, solo sciatto (ad esempio, l'handle potrebbe essere una variabile non inizializzata).

Un tipo esistenziale è nascosto dal tuo programma. Se fopenrestituito un tipo esistenziale, tutto ciò che potresti fare è usarlo con alcune funzioni di libreria che accettano questo tipo esistenziale. Ad esempio, il seguente pseudo-codice verrà compilato:

let exfile = fopen("foo.txt"); // No type for exfile!
read(exfile, buf, size);

L'interfaccia "read" è dichiarata come:

Esiste un tipo T tale che:

size_t read(T exfile, char* buf, size_t size);

L'esfile della variabile non è un file int, non un char*, non uno struct, niente che puoi esprimere nel sistema dei tipi. Non puoi dichiarare una variabile il cui tipo è sconosciuto e non puoi lanciare, diciamo, un puntatore in quel tipo sconosciuto. La lingua non ti permetterà.


9
Questo non funzionerà. Se la firma di readè ∃T.read(T file, ...)allora non c'è niente che puoi passare come primo parametro. Ciò che funzionerebbe sarebbe fopenrestituire l'handle del file e una funzione di lettura nell'ambito dello stesso esistenziale :∃T.(T, read(T file, ...))
Kannan Goundan,

2
Questo sembra parlare solo di ADT.
kizzx2,

7

Sembra che sto arrivando un po 'in ritardo, ma comunque, questo documento aggiunge un'altra visione di quali siano i tipi esistenziali, anche se non specificatamente indipendenti dalla lingua, dovrebbe essere quindi abbastanza più semplice capire i tipi esistenziali: http: //www.cs.uu .nl / groups / ST / Projects / ehc / ehc-book.pdf (capitolo 8)

La differenza tra un tipo quantificato universalmente ed esistenzialmente può essere caratterizzata dalla seguente osservazione:

  • L'uso di un valore con un tipo quantificato determina il tipo da scegliere per l'istanza della variabile di tipo quantificata. Ad esempio, il chiamante della funzione di identità "id :: ∀aa → a" determina il tipo da scegliere per la variabile di tipo a per questa particolare applicazione di id. Per l'applicazione della funzione "id 3" questo tipo è uguale a Int.

  • La creazione di un valore con un tipo quantificato determina e nasconde il tipo della variabile di tipo quantificato. Ad esempio, un creatore di un "∃a. (A, a → Int)" potrebbe aver costruito un valore di quel tipo da "(3, λx → x)"; un altro creatore ha costruito un valore con lo stesso tipo da “('x', λx → ord x)”. Dal punto di vista degli utenti, entrambi i valori hanno lo stesso tipo e sono quindi intercambiabili. Il valore ha un tipo specifico scelto per la variabile di tipo a, ma non sappiamo quale tipo, quindi queste informazioni non possono più essere sfruttate. Queste informazioni specifiche sul tipo di valore sono state "dimenticate"; sappiamo solo che esiste.


1
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
Sheilak,

1
@sheilak: aggiornata la risposta, grazie per il suggerimento
themarketka

5

Esiste un tipo universale per tutti i valori dei parametri di tipo. Un tipo esistenziale esiste solo per i valori dei parametri di tipo che soddisfano i vincoli del tipo esistenziale.

Ad esempio, in Scala un modo per esprimere un tipo esistenziale è un tipo astratto che è vincolato ad alcuni limiti superiori o inferiori.

trait Existential {
  type Parameter <: Interface
}

Equivalentemente un tipo universale vincolato è un tipo esistenziale come nell'esempio seguente.

trait Existential[Parameter <: Interface]

Qualsiasi sito di utilizzo può utilizzare il Interfaceperché qualsiasi sottotipo di istanza di Existentialdeve definire ciò type Parameterche deve implementare il Interface.

Un caso degenerato di tipo esistenziale in Scala è un tipo astratto a cui non si fa mai riferimento e che pertanto non deve essere definito da nessun sottotipo. Questo ha effettivamente una notazione abbreviata List[_] in Scala e List<?>in Java.

La mia risposta è stata ispirata dalla proposta di Martin Odersky di unificare i tipi astratti ed esistenziali. La diapositiva di accompagnamento aiuta la comprensione.


1
Avendo letto alcuni dei materiali sopra riportati, sembra che tu abbia riassunto bene la mia comprensione: i Tipi universali ∀x.f(x), sono opachi rispetto alle loro funzioni di ricezione mentre i Tipi esistenziali ∃x.f(x), sono costretti ad avere determinate proprietà. In genere, tutti i parametri sono esistenziali poiché la loro funzione li manipolerà direttamente; tuttavia, i parametri generici possono avere tipi universali poiché la funzione non li gestirà oltre le operazioni universali di base come ottenere un riferimento come in:∀x.∃array.copy(src:array[x] dst:array[x]){...}
George

Come descritto qui stackoverflow.com/a/19413755/3195266 i membri del tipo possono emulare la quantificazione universale tramite il tipo di identità. E di sicuro c'èforSome quantificazione esistenziale dei parametri di tipo.
Netsu

3

La ricerca su tipi di dati astratti e il nascondimento delle informazioni ha introdotto tipi esistenziali nei linguaggi di programmazione. Rendere un abstract di tipo di dati nasconde informazioni su quel tipo, quindi un client di quel tipo non può abusarne. Supponiamo che tu abbia un riferimento a un oggetto ... alcune lingue ti permettono di lanciare quel riferimento a un riferimento a byte e fare tutto ciò che vuoi su quel pezzo di memoria. Ai fini di garantire il comportamento di un programma, è utile che un linguaggio imponga di agire solo sul riferimento all'oggetto tramite i metodi forniti dal progettista dell'oggetto. Sai che esiste il tipo, ma niente di più.

Vedere:

I tipi astratti hanno tipo esistenziale, MITCHEL e PLOTKIN

http://theory.stanford.edu/~jcm/papers/mitch-plotkin-88.pdf


1

Ho creato questo diagramma. Non so se sia rigoroso. Ma se aiuta, sono contento. inserisci qui la descrizione dell'immagine


-6

A quanto ho capito, è un modo matematico per descrivere interfacce / classe astratta.

Per quanto riguarda T = ∃X {X a; int f (X); }

Per C # si tradurrebbe in un tipo astratto generico:

abstract class MyType<T>{
    private T a;

    public abstract int f(T x);
}

"Esistenziale" significa solo che esiste un tipo che obbedisce alle regole qui definite.

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.