classe in lingua e tipo OOP


9

Nella teoria del linguaggio di programmazione, un tipo è un insieme di valori. Ad esempio, il tipo "int" è l'insieme di tutti i valori interi.

Nei linguaggi OOP, una classe è un tipo, vero?

Quando viene definita una classe con più di un membro, ad es

class myclass{
    int a; 
    double b;
}

Quando parliamo di una lezione, intendiamo

  • " (a,b)dove aè un int ed bè un doppio", oppure
  • "{ (x,y)| xè un int, yè un doppio}"?

Cosa significa un'istanza di myclass?

  • " (a,b)dove aè un int ed bè un doppio", oppure
  • un oggetto che occupa uno spazio di memoria e che può (non necessariamente, cioè può essere vuoto) memorizzare (x,y), dov'è xqualsiasi int ed yè un doppio?

2
Una classe è un tipo. "{(x, y) | x è qualsiasi int, y è un doppio}" " sarebbe quasi corretto, tranne per due cose: 1) hai usato una tupla mentre una classe è concettualmente un record - fai riferimento al suo campi per nome, non posizione; e 2) Non tutti i record con campi ae bsono membri di quel tipo, come menziona Killian Forth. Myclass è isomorfo per i record con campi ae bdi tipo inte double- potresti prendere un record del genere e trasformarlo in un esempio di myclass.
Doval,

1
In linguaggi fortemente tipizzati, una classe è un tipo. Nelle lingue debolmente tipizzate, può essere o meno un tipo.
shawnhcorey,

1
Nella teoria del linguaggio di programmazione, un tipo è un insieme di valori? Penso che devi procurarti un altro libro o un altro insegnante o entrambi. Una 'variabile' o una 'costante' ha un 'tipo' e spesso ha un 'valore'. Esistono tipi di valore zero, scalari e tipi di valore composito in cui il valore della variabile o costante contiene sotto-variabili / sottocostanti.
user1703394,

1
@ user1703394 Un tipo è un insieme di valori. Un tipo intero a 32 bit è un insieme di 2 ^ 32 valori distinti. Se un'espressione restituisce un valore di quel tipo, sai che il valore è in quell'insieme. Gli operatori matematici sono solo funzioni rispetto ai valori di quell'insieme.
Doval,

1
Sarei anche cauto considerando i tipi come insiemi di valori. Gli insiemi hanno relazioni che non sono strettamente applicabili ai tipi. Per concettualizzare i tipi è un buon modello, ma si rompe una volta che inizi a guardare le cose più da vicino - e anche più quando introduci il sottotipo.
Telastyn,

Risposte:


30

Nessuno dei due.

Suppongo che tu stia chiedendo se avere lo stesso set di tipi di campo sia sufficiente per classificarlo come la stessa classe o se debbano essere nominati in modo identico. La risposta è: "Non basta nemmeno avere gli stessi tipi e gli stessi nomi!" Le classi strutturalmente equivalenti non sono necessariamente compatibili con il tipo.

Ad esempio, se si dispone di una CartesianCoordinatese una PolarCordinatesclasse, potrebbero avere entrambi due numeri nei rispettivi campi e potrebbero persino avere lo stesso Numbertipo e gli stessi nomi, ma non sarebbero comunque compatibili e un'istanza di PolarCoordinatesnon sarebbe un istanza di CartesianCoordinates. La capacità di separare i tipi in base allo scopo previsto e non all'implementazione corrente è una parte molto utile per scrivere codice più sicuro e più gestibile.


9
Va notato che in alcune lingue essere strutturalmente equivalenti è sufficiente per rendere un tipo un sottotipo dell'altro (e spesso viceversa). Ciò è decisamente insolito / impopolare.
Telastyn,

7
@Tim Un typedef non crea un tipo, alias il nome utilizzato per fare riferimento a un tipo esistente.
Doval,

1
@DevSolar Ha menzionato esplicitamente C e, a parte C ++, non conosco nessun altro linguaggio che usi quella parola chiave.
Doval,

3
@Telastyn: quelle lingue dovrebbero essere uccise con il fuoco.
Jon Story,

4
@JonStory Il sottotipo strutturale è utile a livello di modulo; la mancanza di ciò è ciò che ti costringe a trasformare tutto in un interfaceJava e C # se mai vuoi fare test di unità. Finisci per scrivere un sacco di boilerplate solo così puoi cambiare la classe particolare che il tuo programma userà anche se non hai alcuna intenzione di cambiarlo in fase di esecuzione.
Doval,

6

I tipi non sono insiemi.

Vedete, la teoria degli insiemi ha una serie di caratteristiche che semplicemente non si applicano ai tipi e viceversa . Ad esempio, un oggetto ha un solo tipo canonico. Può essere un'istanza di diversi tipi diversi, ma solo uno di questi tipi è stato utilizzato per istanziarlo. La teoria degli insiemi non ha nozioni di insiemi "canonici".

La teoria degli insiemi ti consente di creare sottoinsiemi al volo , se hai una regola che descrive ciò che appartiene al sottoinsieme. La teoria dei tipi generalmente non lo consente. Mentre la maggior parte delle lingue ha un Numbertipo o qualcosa di simile, non ne ha un EvenNumbertipo, né sarebbe semplice crearne uno. Voglio dire, è abbastanza facile definire il tipo stesso, ma qualsiasi Numbers esistente che si presenta anche non verrà trasformata magicamente in EvenNumbers.

In realtà, dire che è possibile "creare" sottoinsiemi è un po 'disonesto, perché gli insiemi sono un diverso tipo di animale. Nella teoria degli insiemi, questi sottoinsiemi esistono già , in tutti gli infiniti modi in cui puoi definirli. Nella teoria dei tipi, di solito ci aspettiamo di avere a che fare con un numero finito (se grande) di tipi in un dato momento. Gli unici tipi che si dice esistano sono quelli che abbiamo effettivamente definito, non tutti i tipi che potremmo definire.

Gli insiemi non possono contenere direttamente o indirettamente se stessi . Alcuni linguaggi, come Python, forniscono tipi con strutture meno regolari (in Python, typeil tipo canonico è typeed objectè considerato un'istanza di object). D'altra parte, la maggior parte delle lingue non consente ai tipi definiti dall'utente di impegnarsi in questo tipo di inganno.

Gli insiemi possono comunemente sovrapporsi senza essere contenuti l'uno nell'altro. Questo è raro nella teoria dei tipi, sebbene alcune lingue lo supportino sotto forma di eredità multipla. Altre lingue, come Java, consentono solo una forma limitata di questo o non lo consentono del tutto.

Il tipo vuoto esiste (si chiama il tipo in basso ), ma la maggior parte delle lingue non lo supporta o non lo considera un tipo di prima classe. Esiste anche il "tipo che contiene tutti gli altri tipi" (si chiama il tipo superiore ) ed è ampiamente supportato, diversamente dalla teoria degli insiemi.

NB : Come alcuni commentatori hanno precedentemente sottolineato (prima che il thread fosse spostato in chat), è possibile modellare i tipi con la teoria degli insiemi e altri costrutti matematici standard. Ad esempio, è possibile modellare l'appartenenza al tipo come relazione anziché modellare i tipi come set. Ma in pratica, questo è molto più semplice se usi la teoria delle categorie invece della teoria degli insiemi. Ecco come Haskell modella la sua teoria dei tipi, per esempio.


La nozione di "sottotipizzazione" è molto diversa dalla nozione di "sottoinsieme". Se Xè un sottotipo di Y, significa che possiamo sostituire istanze di Yper istanze di Xe il programma continuerà a "funzionare" in un certo senso. Questo è comportamentale piuttosto che strutturale, sebbene alcune lingue (es. Go, Rust, probabilmente C) abbiano scelto quest'ultima per ragioni di convenienza, sia per il programmatore che per l'implementazione del linguaggio.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Ingegnere mondiale il

4

I tipi di dati algebrici sono il modo di discuterne.

Esistono tre modi fondamentali per combinare i tipi:

  • Prodotto. Questo è fondamentalmente quello a cui stai pensando:

    struct IntXDouble{
      int a; 
      double b;
    }
    

    è un tipo di prodotto; i suoi valori sono tutte le possibili combinazioni (es. tuple) di uno inte uno double. Se si considerano i tipi di numero come set, la cardinalità del tipo di prodotto è in realtà il prodotto delle cardinalità dei campi.

  • Somma. Nei linguaggi procedurali questo è un po 'imbarazzante da esprimere direttamente (in genere è fatto con i sindacati con tag ), quindi per una migliore comprensione, ecco un tipo di somma in Haskell:

    data IntOrDouble = AnInt Int
                     | ADouble Double
    

    i valori di questo tipo hanno il modulo AnInt 345o ADouble 4.23, ma c'è sempre un solo numero coinvolto (diversamente dal tipo di prodotto, dove ogni valore ha due numeri). Quindi la cardinalità: prima elenchi tutti i Intvalori, ognuno deve essere combinato con il AnIntcostruttore. Inoltre , tutti i Doublevalori, ciascuno combinato con ADouble. Quindi tipo di somma .

  • Esponenziazione 1 . Non ne parlerò in dettaglio qui perché non ha alcuna chiara corrispondenza OO.

E le lezioni? Ho deliberatamente usato la parola chiave structanziché classper IntXDouble. Il fatto è che una classe come tipo non è in realtà caratterizzata dai suoi campi, questi sono semplicemente dettagli di implementazione. Il fattore cruciale è piuttosto, quali valori distinguibili può avere la classe.

Ciò che è rilevante è che il valore di una classe può essere il valore di una qualsiasi delle sue sottoclassi ! Quindi una classe è in realtà un tipo di somma piuttosto che un tipo di prodotto: se Ae Bda cui entrambi sarebbero derivati myClass, myClasssarebbe essenzialmente la somma di Ae B. Indipendentemente dall'attuazione effettiva.


1 Queste sono funzioni (in senso matematico! ); un tipo di funzione Int -> Doubleè rappresentato dall'esponenziale DoubleInt. Peccato se la tua lingua non ha funzioni adeguate ...


2
Scusa, ma penso che questa sia una risposta molto scadente. Funzioni fare avere una chiara analogico OO, ovvero metodi (e tipi di interfaccia metodo singolo). La definizione base di un oggetto è che ha sia stato (campi / membri dati) sia comportamento (metodi / funzioni membro); la tua risposta ignora quest'ultima.
Ruakh,

@ruakh: no. Naturalmente è possibile implementare funzioni in OO, ma in generale i metodi non sono funzioni ( perché modificano lo stato ecc.). Né le "funzioni" nelle funzioni procedurali dei linguaggi, del resto. In effetti, le interfacce a metodo statico singolo si avvicinano di più ai tipi funzione / esponenziali, ma speravo di evitarne la discussione perché non ha rilevanza per questa domanda.
circa il

... cosa ancora più importante, il mio anwer fa prendere in considerazione il comportamento. In effetti, il comportamento è di solito il motivo per cui si utilizza l'ereditarietà e l'unificazione di diversi comportamenti possibili cattura esattamente l'aspetto di tipo somma delle classi OO.
circa il

@ruakh Un metodo non può senza il suo oggetto. L'analogo più vicino sono i staticmetodi, tranne per il fatto che non sono ancora valori di prima classe. La cosa sulla maggior parte dei linguaggi OO è che prendono gli oggetti come il più piccolo blocco di costruzione, quindi se vuoi qualcosa di più piccolo devi fingere con oggetti, e finisci comunque per trascinare in un mucchio di funzioni semantiche che non dovrebbero avere. Ad esempio, non ha senso confrontare le funzioni per l'uguaglianza, ma è comunque possibile confrontare due oggetti con funzioni false.
Doval,

@Doval 1) puoi passare metodi intorno ad AFAIK, quindi sono valori di prima classe; 2) ha senso confrontare le funzioni per l'uguaglianza, le persone JS lo fanno sempre.
Den

2

Scusa ma non conosco la teoria "grezza". Posso solo fornire un approccio pratico. Spero che questo sia accettabile su programmers.SE; Non ho familiarità con l'etichetta qui.


Un tema centrale di OOP è nascondere le informazioni . Quali siano esattamente i membri dei dati di una classe non dovrebbero interessare i suoi clienti. Un client invia messaggi a (chiama metodi / funzioni membro di) un'istanza, che potrebbe o meno modificare lo stato interno. L'idea è che gli interni di una classe potrebbero cambiare, senza che il client ne sia influenzato.

Un correlativo a questo è che la classe è responsabile di garantire che la sua rappresentazione interna rimanga "valida". Supponiamo che una classe memorizzi un numero di telefono (semplificato) in due numeri interi:

    int areacode;
    int number;

Questi sono i membri dei dati della classe. Tuttavia, la classe sarà probabilmente molto più che solo i suoi membri di dati, e certamente non è definibile come "insieme di tutti i possibili valori di int x int". Non dovresti avere accesso diretto ai membri dei dati.

La costruzione di un'istanza potrebbe negare qualsiasi numero negativo. Forse la costruzione avrebbe anche normalizzato il prefisso in qualche modo, o addirittura verificato l'intero numero. In questo modo finiresti molto più vicino al tuo "(a,b) where a is an int and b is a double", perché certamente non ci sono due int memorizzati in quella classe.

Ma non importa davvero per quanto riguarda la classe. Non è il tipo di membri dei dati, né l'intervallo dei loro possibili valori che definisce la classe, sono i metodi che sono definiti per essa.

Fintanto che tali metodi rimangono gli stessi, l'implementatore potrebbe cambiare i tipi di dati in virgola mobile, BIGNUM, stringhe, qualunque cosa, e per tutti gli scopi pratici, rimarrebbe comunque la stessa classe .


Esistono schemi di progettazione per garantire che tali modifiche della rappresentazione interna possano essere apportate senza che il client ne sia nemmeno a conoscenza (ad esempio il linguaggio del pimpl in C ++, che nasconde tutti i membri dei dati dietro un puntatore opaco ).


1
It is neither the type of the data members, nor the range of their possible values that defines the class, it's the methods that are defined for it.I membri dei dati non definiscono una classe solo quando li nascondi. Questo potrebbe essere il caso più comune, ma certamente non si può dire che sia vero per tutte le classi. Se anche un singolo campo è pubblico è importante quanto i suoi metodi.
Doval,

1
A meno che tu non stia codificando in Java, dove non hai scelta in materia e anche i tuoi stupidi record falsi senza comportamento devono essere classes. (Contrassegnarli finalaiuta a capire il punto, ma comunque). protectedTuttavia, hai ancora un problema con i membri, che possono essere ereditati e quindi far parte di una seconda API per gli implementatori di sottoclassi.
Doval,

1
@Doval: ho capito che si trattava di una domanda "teorica", motivo per cui sono rimasto il più chiaro possibile rispetto alle attuali problematiche linguistiche. (Proprio come se fossi il più libero da Java e protectedpossibile nella pratica. ;-))
DevSolar

3
Il problema è che a classè un costrutto dipendente dalla lingua. Per quanto ne so, non esiste una classteoria dei tipi.
Doval,

1
@Doval: Ciò non significherebbe che la teoria dei tipi in non si applica alle classi, in quanto sono un costrutto fuori dallo scopo di quella teoria?
DevSolar,

2
  • Un tipo è una descrizione di una categoria / intervallo di valori, strutture composte o cosa hai. OOPwise, è simile a una "interfaccia". (Nel senso agnostico del linguaggio. Il senso specifico del linguaggio, non tanto. In Java, ad esempio, intè un tipo , ma non ha alcuna relazione con un interface. Le specifiche del campo pubblico / protetto, inoltre, non fanno parte di un interface, ma fanno parte di una "interfaccia" o tipo .)

    Il punto principale è che è molto più una definizione semantica che una concreta. La struttura prende in considerazione solo i fattori in quanto i campi / comportamenti esposti e i loro scopi definiti si allineano. Se non si dispone di entrambi, non si dispone della compatibilità dei tipi.

  • Una classe è la realizzazione di un tipo. È un modello che definisce effettivamente la struttura interna, il comportamento associato, ecc.

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.