Omissione dell'ereditarietà nei linguaggi di programmazione

10

Sto sviluppando il mio linguaggio di programmazione. È un linguaggio generico (pensa Python statico tipicamente per il desktop, cioè int x = 1; ) non destinato al cloud.

Pensi che sia giusto non consentire l'ereditarietà o i Mixin? (dato che l'utente avrebbe almeno interfacce)

Ad esempio: Google Go, Un linguaggio di sistema che ha scioccato la comunità di programmazione non consentendo l'ereditarietà.

    
posta Christopher 17.01.2012 - 01:24
fonte

9 risposte

4

In effetti, scopriamo sempre di più che l'utilizzo dell'ereditarietà è subottimale perché (tra gli altri) porta a un accoppiamento stretto.

L'ereditarietà dell'implementazione può essere sempre sostituita dall'ereditarietà dell'interfaccia e dalla composizione, e i progetti di software più moderni tendono ad andare nella direzione dell'uso dell'ereditarietà sempre meno, a favore della composizione.

Quindi yes , non fornire l'ereditarietà in particolare è una decisione di progettazione completamente valida e molto in voga .

I Mixin, d'altra parte, non sono nemmeno (ancora) una caratteristica della lingua principale, e le lingue che fanno forniscono una funzione chiamata "mixin" spesso capiscono cose molto diverse da essa. Sentiti libero di fornirlo, o no. Personalmente lo trovo molto utile, ma implementarlo correttamente potrebbe essere molto difficile.

    
risposta data 17.01.2012 - 12:40
fonte
13

Ragionare sul fatto che l'ereditarietà (o qualsiasi singola caratteristica, in realtà) sia necessaria o meno senza considerare anche il resto della semantica del linguaggio è inutile; stai litigando nel vuoto.

Ciò di cui hai bisogno è una filosofia di progettazione linguistica coerente; il linguaggio deve essere in grado di risolvere elegantemente i problemi per i quali è stato progettato. Il modello per ottenere questo può o non può richiedere l'ereditarietà, ma è difficile giudicarlo senza il quadro generale.

Se, ad esempio, la tua lingua ha funzioni di prima classe, applicazioni a funzioni parziali, tipi di dati polimorfici, variabili di tipo e tipi generici, hai praticamente coperto le stesse basi che avresti con l'ereditarietà OOP classica, ma usando un paradigma diverso.

Se hai un'associazione tardiva, una digitazione dinamica, metodi come proprietà, argomenti di funzioni flessibili e funzioni di prima classe, copri anche gli stessi motivi, ma di nuovo, usando un paradigma diverso.

(Trovare esempi per i due paradigmi descritti è lasciato come esercizio per il lettore.)

Quindi, pensa al tipo di semantica che vuoi, gioca con loro e vedi se sono sufficienti senza ereditarietà. Se non lo sono, puoi decidere di trasferire l'ereditarietà nel mix o decidere che manchi qualcosa.

    
risposta data 17.01.2012 - 11:15
fonte
9

Sì.

Penso che non permetta l'ereditarietà, specialmente se la tua lingua è tipizzata dinamicamente. È possibile ottenere un riutilizzo del codice simile, ad esempio delegazione o composizione. Ad esempio, ho scritto alcuni programmi moderatamente complicati - ok, non che complicati:) - in JavaScript senza alcuna ereditarietà. Fondamentalmente ho usato oggetti come strutture dati algebriche con alcune funzioni collegate come metodi. Avevo anche un sacco di funzioni che erano i metodi non che funzionavano su questi oggetti.

Se hai digitazione dinamica, e presumo che tu lo faccia, puoi anche avere il polimorfismo senza ereditarietà. Inoltre, se consenti l'aggiunta di metodi arbitrari agli oggetti in fase di runtime, non avrai davvero bisogno di cose come i mixin.

Un'altra opzione, che ritengo sia buona, è quella di emulare JavaScript e utilizzare i prototipi. Questi sono più semplici delle classi e anche molto flessibili. Solo qualcosa da considerare.

Quindi, detto tutto, ci proverei.

    
risposta data 17.01.2012 - 02:19
fonte
8

Sì, è una decisione progettuale perfettamente ragionevole di omettere l'ereditarietà.

Esistono in effetti ottime ragioni per rimuovere l'ereditarietà dell'implementazione, in quanto può produrre codice estremamente complesso e difficile da gestire. Mi spingerei fino al punto di considerare l'ereditarietà (come è tipicamente implementata nella maggior parte dei linguaggi OOP) come una disfunzione.

Clojure, ad esempio, non fornisce l'ereditarietà dell'implementazione, preferendo fornire un insieme di caratteristiche ortogonali (protocolli, dati, funzioni, macro) che possono essere utilizzate per ottenere gli stessi risultati ma molto più chiaramente.

Ecco un video che ho trovato molto illuminante su questo argomento generale, in cui Rich Hickey identifica le principali fonti di complessità nei linguaggi di programmazione (inclusa l'ereditarietà) e presenta alternative per ciascuna: Semplificazione semplice

    
risposta data 17.01.2012 - 02:53
fonte
4

Quando ho scoperto per la prima volta che le classi VB6 non supportano l'ereditarietà (solo le interfacce), mi ha davvero infastidito (e lo è ancora).

Tuttavia, il motivo è così brutto che non ha nemmeno i parametri del costruttore, quindi non si può fare una normale dipendenza (DI). Se hai DI allora è più importante dell'eredità perché puoi seguire il principio di favorire la composizione sull'ereditarietà. Questo è un modo migliore per riutilizzare il codice comunque.

Non avendo Mixins? Se si desidera implementare un'interfaccia e delegare tutto il lavoro di tale interfaccia a un insieme di oggetti tramite l'integrazione delle dipendenze, i Mixin sono l'ideale. Altrimenti è necessario scrivere tutto il codice boilerplate per delegare ogni metodo e / o proprietà all'oggetto figlio. Lo faccio molto (grazie a C #) ed è una cosa che vorrei non dover fare.

    
risposta data 17.01.2012 - 02:23
fonte
2

Per non essere d'accordo con un'altra risposta: no, si eliminano le funzionalità quando sono incompatibili con qualcosa che si desidera di più. Java (e altri linguaggi di GC) ha gettato fuori la gestione della memoria esplicita perché voleva più sicurezza di tipo. Haskell ha gettato la mutazione perché voleva più ragionamento equazionale e tipi di fantasia. Anche C ha gettato (o dichiarato illegale) certi tipi di alias e altri comportamenti perché voleva più ottimizzazioni del compilatore.

Quindi la domanda è: cosa vuoi di più dell'ereditarietà?

    
risposta data 17.01.2012 - 04:59
fonte
1

No.

Se si desidera rimuovere una caratteristica della lingua di base, si dichiara universalmente che non è mai, mai necessario (o ingiustificatamente difficile da implementare, che non si applica qui). E "mai" è una parola strong nell'ingegneria del software. Avresti bisogno di una giustificazione molto potente per fare una simile affermazione.

    
risposta data 17.01.2012 - 02:22
fonte
1

Parlando da un punto di vista strettamente C ++, l'ereditarietà è utile per due scopi principali:

  1. Consente la resusabilità del codice
  2. In combinazione con l'override in una classe figlia, consente di utilizzare un puntatore di classe base per gestire un oggetto classe figlio senza conoscerne il tipo.

Per il punto 1, purché tu abbia un modo per condividere il codice senza doverti indugiare in troppe acrobazie, puoi eliminare l'ereditarietà. Per il punto 2. Puoi andare in modo java e insistere su un'interfaccia per implementare questa funzione.

I vantaggi della rimozione dell'ereditarietà sono

  1. previene lunghe gerarchie e i relativi problemi. Il tuo meccanismo di code-sharing dovrebbe essere abbastanza buono per questo.
  2. Evita i cambiamenti nelle classi genitore rompendo le classi figlie.

Il compromesso è in gran parte tra "Non ripetere te stesso" e Flessibilità da un lato e il problema di evitare dall'altro lato. Personalmente odio vedere l'ereditarietà passare dal C ++ semplicemente perché alcuni sviluppatori potrebbero non essere abbastanza intelligenti da prevedere i problemi.

    
risposta data 17.01.2012 - 09:16
fonte
1

Do you think it's okay not to allow inheritance or Mixins? (given that the user would at least have interfaces)

Puoi fare un lavoro molto utile senza ereditarietà o mixin di implementazione , ma mi chiedo se tu debba avere un qualche tipo di ereditarietà dell'interfaccia, cioè una dichiarazione che dice se un oggetto implementa l'interfaccia A quindi ha anche bisogno di interfaccia B (cioè, A è una specializzazione di B e quindi c'è una relazione di tipo). D'altra parte, l'oggetto risultante deve solo registrare che implementa entrambe le interfacce, quindi non c'è troppa complessità lì. Tutto perfettamente realizzabile.

La mancanza di ereditarietà dell'implementazione ha però uno svantaggio chiaro: non sarà possibile costruire vtables con indici numerici per le classi e quindi dovrà fare ricerche hash per ogni chiamata di metodo (o trovare un modo intelligente per evitarli) . Ciò potrebbe essere doloroso se instradi anche valori fondamentali (ad es. Numeri) attraverso questo meccanismo. Anche un'implementazione di hash molto buona può essere costosa quando la colpisci più volte in ogni loop interno!

    
risposta data 17.01.2012 - 11:39
fonte

Leggi altre domande sui tag