C'è un vero vantaggio nei linguaggi dinamici? [chiuso]


29

Innanzitutto voglio dire che Java è l'unica lingua che abbia mai usato, quindi per favore scusa la mia ignoranza su questo argomento.

Le lingue tipizzate in modo dinamico consentono di inserire qualsiasi valore in qualsiasi variabile. Quindi, ad esempio, potresti scrivere la seguente funzione (psuedocode):

void makeItBark(dog){
    dog.bark();
}

E puoi passarci dentro qualunque valore. Finché il valore ha un bark()metodo, il codice verrà eseguito. Altrimenti, viene generata un'eccezione di runtime o qualcosa di simile. (Per favore, correggimi se sbaglio su questo).

Apparentemente, questo ti dà flessibilità.

Tuttavia, ho fatto qualche lettura su linguaggi dinamici e quello che la gente dice è che quando si progetta o si scrive codice in un linguaggio dinamico, si pensa ai tipi e li si tiene in considerazione, proprio come si farebbe in un linguaggio tipicamente statico.

Quindi, ad esempio, quando scrivi la makeItBark()funzione, intendi che accetti solo "cose ​​che possono abbaiare" e devi comunque assicurarti di passarci solo questo tipo di cose. L'unica differenza è che ora il compilatore non ti dirà quando hai fatto un errore.

Certo, c'è un vantaggio in questo approccio che è che nei linguaggi statici, per ottenere "questa funzione accetta tutto ciò che può abbaiare", è necessario implementare un'interfaccia esplicita Barker. Tuttavia, questo sembra un vantaggio minore.

Mi sto perdendo qualcosa? Cosa sto effettivamente guadagnando usando un linguaggio tipizzato in modo dinamico?


6
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof")). Quell'argomento non è nemmeno una classe , è una tupla anonima. La digitazione Duck ("se si blocca come un ...") ti consente di realizzare interfacce ad hoc con restrizioni essenzialmente zero e nessun sovraccarico sintattico. Puoi farlo in un linguaggio come Java, ma finisci con un sacco di riflessioni disordinate. Se una funzione in Java richiede un ArrayList e vuoi assegnargli un altro tipo di raccolta, sei SOL. In pitone che non può nemmeno venire fuori.
Phoshi,

2
Questo tipo di domanda è stata posta prima: qui , qui e qui . Nello specifico il primo esempio sembra rispondere alla tua domanda. Forse puoi riformulare il tuo per renderlo distinto?
logc,

3
Nota che ad esempio in C ++ puoi avere una funzione template che funziona con qualsiasi tipo T che ha un bark()metodo, con il compilatore che si lamenta quando passi qualcosa di sbagliato ma senza dover dichiarare effettivamente un'interfaccia che contiene bark ().
Wilbert,

2
@Phoshi L'argomento in Python deve ancora essere di un tipo particolare - ad esempio, non può essere un numero. Se hai la tua implementazione ad hoc di oggetti, che recupera i suoi membri attraverso alcune getMemberfunzioni personalizzate , makeItBarkesplode perché hai chiamato dog.barkinvece di dog.getMember("bark"). Ciò che fa funzionare il codice è che tutti accettano implicitamente di usare il tipo di oggetto nativo di Python.
Doval,

2
@Phoshi Just because I wrote makeItBark with my own types in mind doesn't mean you can't use yours, wheras in a static language it probably /does/ mean that.Come indicato nella mia risposta, questo non è il caso in generale . È il caso di Java e C #, ma quei linguaggi hanno paralizzato i sistemi di tipo e modulo, quindi non sono rappresentativi di ciò che può fare la tipizzazione statica. Posso scrivere un makeItBarklinguaggio perfettamente generico in diversi linguaggi tipicamente statici, anche in quelli non funzionali come C ++ o D.
Doval

Risposte:


35

Le lingue tipizzate dinamicamente sono tipizzate uni

Confrontando i sistemi di tipi , non c'è alcun vantaggio nella digitazione dinamica. La tipizzazione dinamica è un caso speciale di tipizzazione statica : è un linguaggio di tipo statico in cui ogni variabile ha lo stesso tipo. È possibile ottenere la stessa cosa in Java (meno concisione) rendendo ogni variabile di tipo Objecte avendo valori di "oggetto" di tipo Map<String, Object>:

void makeItBark(Object dog) {
    Map<String, Object> dogMap = (Map<String, Object>) dog;
    Runnable bark = (Runnable) dogMap.get("bark");
    bark.run();
}

Quindi, anche senza riflessione, puoi ottenere lo stesso effetto in quasi ogni linguaggio tipicamente statico, a parte la convenienza sintattica. Non stai ottenendo alcun potere espressivo aggiuntivo; al contrario, hai meno potere espressivo perché in un linguaggio tipizzato in modo dinamico, ti viene negata la possibilità di limitare le variabili a determinati tipi.

Produrre una corteccia d'anatra in un linguaggio tipicamente statico

Inoltre, un buon linguaggio tipicamente statico ti permetterà di scrivere codice che funziona con qualsiasi tipo che abbia barkun'operazione. In Haskell, questa è una classe di tipo:

class Barkable a where
    bark :: a -> unit

Ciò esprime il vincolo secondo cui per un tipo ada considerare Barkable, deve esistere una barkfunzione che assume un valore di quel tipo e non restituisce nulla.

È quindi possibile scrivere funzioni generiche in termini di Barkablevincolo:

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

Questo dice che makeItBarkfunzionerà per qualsiasi tipo di Barkablerequisito soddisfacente. Questo potrebbe sembrare simile a un interfacein Java o C # ma ha un grande vantaggio: i tipi non devono specificare in anticipo quali classi di tipi soddisfano. Posso dire che il tipo Duckè Barkablein qualsiasi momento, anche se Duckè un tipo di terze parti che non ho scritto. In realtà, non importa che lo scrittore Ducknon abbia scritto una barkfunzione: posso fornirlo dopo il fatto quando dico la lingua che Ducksoddisfa Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))

makeItBark (aDuck)

Questo dice che i Ducks possono abbaiare e la loro funzione di corteccia è implementata dando un pugno all'anatra prima di farlo ciarlare. Detto questo, possiamo fare appello makeItBarkalle anatre.

Standard MLe OCamlsono ancora più flessibili in quanto puoi soddisfare la stessa classe di tipo in più di un modo. In queste lingue posso dire che gli interi possono essere ordinati usando l'ordinamento convenzionale e quindi girare e dire che sono ordinabili anche per divisibilità (ad esempio 10 > 5perché 10 è divisibile per 5). In Haskell è possibile creare un'istanza di una classe di tipo una sola volta. (Ciò consente a Haskell di sapere automaticamente che è possibile chiamare barkun'anatra; in SML o OCaml devi essere esplicito su quale bark funzione vuoi, perché potrebbe essercene più di una.)

conciseness

Certo, ci sono differenze sintattiche. Il codice Python che hai presentato è molto più conciso dell'equivalente Java che ho scritto. In pratica, quella concisione è una grande parte del fascino dei linguaggi tipizzati dinamicamente. Ma l'inferenza del tipo ti consente di scrivere un codice altrettanto conciso nei linguaggi tipicamente statici, sollevandoti dal dover scrivere esplicitamente i tipi di ogni variabile. Un linguaggio tipicamente statico può anche fornire supporto nativo per la tipizzazione dinamica, eliminando la verbosità di tutte le manipolazioni di casting e mappe (ad es. C # dynamic).

Programmi corretti ma mal digitati

Per essere onesti, la tipizzazione statica esclude necessariamente alcuni programmi che sono tecnicamente corretti anche se il controllo del tipo non può verificarlo. Per esempio:

if this_variable_is_always_true:
    return "some string"
else:
    return 6

La maggior parte delle lingue tipicamente statiche respingerebbe questa ifaffermazione, anche se il ramo else non si verificherà mai. In pratica sembra che nessuno faccia uso di questo tipo di codice - qualcosa di troppo intelligente per la verifica dei tipi probabilmente farà in modo che i futuri manutentori del tuo codice malediscano te e il tuo parente prossimo. Caso in questione, qualcuno ha tradotto con successo 4 progetti Python open source in Haskell, il che significa che non stavano facendo nulla che un buon linguaggio tipicamente statico non potesse compilare. Inoltre, il compilatore ha rilevato un paio di bug relativi al tipo che i test unitari non stavano rilevando.

L'argomento più forte che ho visto per la digitazione dinamica sono le macro di Lisp, poiché consentono di estendere arbitrariamente la sintassi del linguaggio. Tuttavia, Typed Racket è un dialetto di Lisp tipicamente statico che ha macro, quindi sembra che la tipizzazione statica e le macro non si escludano a vicenda, anche se forse sono più difficili da implementare contemporaneamente.

Mele e arance

Infine, non dimenticare che ci sono differenze più grandi nelle lingue rispetto al loro sistema di tipi. Prima di Java 8, fare qualsiasi tipo di programmazione funzionale in Java era praticamente impossibile; un semplice lambda richiederebbe 4 righe di codice anonimo di classe di boilerplate. Java non supporta inoltre i letterali di raccolta (ad es [1, 2, 3].). Possono esserci anche differenze nella qualità e nella disponibilità di strumenti (IDE, debugger), librerie e supporto della comunità. Quando qualcuno ha affermato di essere più produttivo in Python o Ruby rispetto a Java, è necessario tenere conto di tale disparità. C'è una differenza tra il confronto delle lingue con tutte le batterie incluse , i core della lingua e i sistemi di tipi .


2
Hai dimenticato di attribuire la tua fonte per il primo paragrafo - existentialtype.wordpress.com/2011/03/19/…

2
@Matt Re: 1, non ho assunto che non sia importante; L'ho affrontato sotto Conciseness. Ri: 2, anche se non l'ho mai detto esplicitamente, per "buono" intendo "ha una completa deduzione del tipo" e "ha un sistema di moduli che ti permette di abbinare il codice per digitare le firme dopo il fatto ", non in anticipo come Java / Le interfacce di C #. Per quanto riguarda 3, l'onere della prova spetta a me spiegarmi come dati due lingue con sintassi e caratteristiche equivalenti, una tipizzata in modo dinamico e l'altra con inferenza di tipo completa, non si sarebbe in grado di scrivere un codice di uguale lunghezza in entrambi .
Doval,

3
@MattFenwick L'ho già giustificato: date due lingue con le stesse caratteristiche, una tipizzata in modo dinamico e l'altra digitata staticamente, la differenza principale tra loro sarà la presenza di annotazioni di tipo e l'inferenza di tipo lo toglierà. Qualsiasi altra differenza nella sintassi è superficiale e qualsiasi differenza nelle caratteristiche trasforma il confronto in mele vs arance. Sta a te mostrare come questa logica sia sbagliata.
Doval,

1
Dovresti dare un'occhiata a Boo. È staticamente digitato con l'inferenza del tipo e ha macro che consentono di estendere la sintassi della lingua.
Mason Wheeler,

1
@Doval: True. A proposito, la notazione lambda non viene utilizzata esclusivamente nella programmazione funzionale: per quanto ne so, Smalltalk ha blocchi anonimi e Smalltalk è il più orientato agli oggetti che può ottenere. Quindi, spesso la soluzione è passare un blocco di codice anonimo con alcuni parametri, indipendentemente dal fatto che si tratti di una funzione anonima o di un oggetto anonimo con esattamente un metodo anonimo. Penso che questi due costrutti esprimano essenzialmente la stessa idea da due diverse prospettive (quella funzionale e quella orientata agli oggetti).
Giorgio,

11

Questo è un problema difficile e abbastanza soggettivo. (E la tua domanda potrebbe chiudersi come basata sull'opinione, ma ciò non significa che sia una cattiva domanda - al contrario, anche pensare a tali domande meta-linguistiche è un buon segno - non è proprio adatto al formato di domande e risposte di questo forum.)

Ecco il mio punto di vista: il punto delle lingue di alto livello è limitare ciò che un programmatore può fare con il computer. Questo è sorprendente per molte persone, poiché credono che lo scopo sia quello di dare agli utenti più potere e ottenere di più . Ma poiché tutto ciò che scrivi in ​​Prolog, C ++ o List viene infine eseguito come codice macchina, in realtà è impossibile dare al programmatore più potenza di quella già fornita dal linguaggio assembly.

Lo scopo di un linguaggio di alto livello è aiutare il programmatore a comprendere meglio il codice che essi stessi hanno creato e renderli più efficienti nel fare la stessa cosa. Un nome di subroutine è più facile da ricordare di un indirizzo esadecimale. Un contatore di argomenti automatico è più facile da usare di una sequenza di chiamate qui devi ottenere il numero di argomenti esattamente da solo, senza aiuto. Un sistema di tipi va oltre e limita il tipo di argomenti che puoi fornire in un determinato posto.

Qui è dove la percezione delle persone differisce. Alcune persone (io sono tra queste) pensano che fintanto che la routine di controllo della password si aspetta comunque esattamente due argomenti, e sempre una stringa seguita da un ID numerico, è utile dichiararlo nel codice ed essere automaticamente avvisato se in seguito ti dimentichi di seguire quella regola. L'outsourcing di questa piccola contabilità al compilatore aiuta a liberare la mente per preoccupazioni di livello superiore e ti rende migliore nella progettazione e nell'architettura del tuo sistema. Pertanto, i sistemi di tipi sono una vincita netta: lasciano che il computer faccia ciò che è bravo e gli umani fanno ciò che sono bravi.

Altri lo vedono in modo molto diverso. Non amano essere informati da un compilatore su cosa fare. A loro non piace lo sforzo extra iniziale di decidere sulla dichiarazione di tipo e digitarla. Preferiscono uno stile di programmazione esplorativa in cui scrivi il vero codice aziendale senza avere un piano che ti dica esattamente quali tipi e argomenti usare dove. E per lo stile di programmazione che usano, potrebbe essere abbastanza vero.

Ovviamente sto semplificando eccessivamente qui. Il controllo del tipo non è strettamente legato alle dichiarazioni esplicite del tipo; c'è anche un'inferenza di tipo. La programmazione con routine che prendono effettivamente argomenti di vario tipo consente cose abbastanza diverse e molto potenti che altrimenti sarebbero impossibili, è solo che molte persone non sono abbastanza attente e coerenti per usare con successo tale margine di manovra.

Alla fine, il fatto che linguaggi così diversi siano entrambi molto popolari e non mostrino segni di morte dimostra che le persone si dedicano alla programmazione in modo molto diverso. Penso che le caratteristiche del linguaggio di programmazione riguardino principalmente i fattori umani - ciò che supporta meglio il processo decisionale umano - e fintanto che le persone lavoreranno in modo molto diverso, il mercato fornirà contemporaneamente soluzioni molto diverse.


3
Grazie per la risposta. Hai detto che ad alcune persone non piace che un compilatore gli dica cosa fare. [..] Preferiscono uno stile di programmazione esplorativa in cui scrivi un vero codice commerciale senza avere un piano che ti dica esattamente quali tipi e argomenti usare dove. " Questa è la cosa che non capisco: la programmazione non è come l'improvvisazione musicale. In musica, se colpisci una nota sbagliata, può sembrare interessante. Nella programmazione, se passi qualcosa in una funzione che non dovrebbe essere lì, molto probabilmente otterrai dei cattivi bug. (continuando nel prossimo commento).
Aviv Cohn,

3
Sono d'accordo, ma molte persone non sono d'accordo. E le persone sono piuttosto possessive riguardo ai loro preconcetti mentali, soprattutto perché spesso non ne sono consapevoli. Ecco perché i dibattiti sullo stile di programmazione di solito degenerano in discussioni o combattimenti, ed è raramente utile avviarli con estranei casuali su Internet.
Kilian Foth,

1
Questo è il motivo per cui - a giudicare da ciò che leggo - le persone che usano i linguaggi dinamici tengono conto dei tipi tanto quanto le persone che usano linguaggi statici. Perché quando scrivi una funzione, dovrebbe prendere argomenti di un tipo specifico. Non importa se il compilatore lo impone o meno. Quindi si riduce alla digitazione statica che ti aiuta in questo, e la digitazione dinamica no. In entrambi i casi, una funzione deve accettare un tipo specifico di input. Quindi non vedo quale sia il vantaggio della digitazione dinamica. Anche se preferisci uno "stile di programmazione esplorativa", non puoi comunque passare ciò che vuoi in una funzione.
Aviv Cohn,

1
Le persone parlano spesso di tipi di progetti molto diversi (soprattutto per quanto riguarda le dimensioni). La logica aziendale per un sito Web sarà molto semplice rispetto a un sistema ERP completo. Ci sono meno rischi di sbagliare e il vantaggio di poter semplicemente riutilizzare un po 'di codice è più rilevante. Supponiamo che io abbia del codice che genera un Pdf (o un po 'di HTML) da una struttura di dati. Ora ho un'origine dati diversa (prima era JSON da alcune API REST, ora è importatore di Excel). In un linguaggio come Ruby può essere semplicissimo "simulare" la prima struttura, "abbaiare" e riutilizzare il codice Pdf.
Thorsten Müller,

@Prog: Il vero vantaggio dei linguaggi dinamici è quando si tratta di descrivere cose che sono davvero difficili con un sistema di tipo statico. Una funzione in Python, ad esempio, potrebbe essere un riferimento a una funzione, una lambda, un oggetto funzione o dio sa cosa e funzionerà allo stesso modo. È possibile creare un oggetto che avvolge un altro oggetto e disponga automaticamente i metodi con zero sovraccarico sintattico e ogni funzione ha essenzialmente magicamente tipi parametrizzati. I linguaggi dinamici sono fantastici per ottenere rapidamente risultati.
Phoshi,

5

Il codice scritto usando linguaggi dinamici non è accoppiato a un sistema di tipo statico. Pertanto, questa mancanza di accoppiamento è un vantaggio rispetto ai sistemi di tipo statico mediocre / inadeguato (sebbene possa essere un lavaggio o uno svantaggio rispetto a un grande sistema di tipo statico).

Inoltre, per un linguaggio dinamico, non è necessario progettare, implementare, testare e mantenere un sistema di tipo statico. Ciò potrebbe rendere l'implementazione più semplice rispetto a una lingua con un sistema di tipo statico.


2
Le persone non tendono infine a reimplementare un sistema di tipo statico di base con i loro test unitari (quando si mira a una buona copertura dei test)?
Den

Cosa intendi per "accoppiamento" qui? Come si manifesterebbe in un'architettura ad esempio di micro-servizi?
Den

@Den 1) buona domanda, tuttavia, ritengo che sia al di fuori dell'ambito del PO e della mia risposta. 2) Intendo accoppiamento in questo senso ; in breve, sistemi di tipo diverso impongono vincoli diversi (incompatibili) al codice scritto in quella lingua. Siamo spiacenti, non posso rispondere all'ultima domanda: non capisco cosa c'è di speciale nei micro-servizi in questo senso.

2
@Den: ottimo punto: osservo spesso che i test unitari che scrivo in Python coprono gli errori che verrebbero rilevati da un compilatore in un linguaggio tipizzato staticamente.
Giorgio,

@MattFenwick: Hai scritto che è un vantaggio che "... per un linguaggio dinamico, un sistema di tipo statico non deve essere progettato, implementato, testato e mantenuto". e Den ha osservato che spesso devi progettare e testare i tuoi tipi direttamente nel tuo codice. Quindi lo sforzo non viene rimosso ma spostato dalla progettazione della lingua al codice dell'applicazione.
Giorgio,
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.