Alla ricerca di chiarimenti su apparenti contraddizioni riguardanti le lingue debolmente tipizzate


178

Penso di capire la tipizzazione forte , ma ogni volta che cerco esempi di ciò che è debole, finisco per trovare esempi di linguaggi di programmazione che semplicemente costringono / convertono automaticamente i tipi.

Ad esempio, in questo articolo chiamato Typing: Strong vs. Weak, Static vs. Dynamic afferma che Python è fortemente tipizzato perché si ottiene un'eccezione se si tenta di:

Pitone

1 + "1"
Traceback (most recent call last):
File "", line 1, in ? 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Tuttavia, una cosa del genere è possibile in Java e in C # e non li consideriamo debolmente digitati solo per quello.

Giava

  int a = 10;
  String b = "b";
  String result = a + b;
  System.out.println(result);

C #

int a = 10;
string b = "b";
string c = a + b;
Console.WriteLine(c);

In questo altro articolo chiamato Weakly Type Languages l'autore afferma che Perl è tipizzato debolmente semplicemente perché posso concatenare una stringa in un numero e viceversa senza alcuna conversione esplicita.

Perl

$a=10;
$b="a";
$c=$a.$b;
print $c; #10a

Quindi lo stesso esempio rende Perl tipizzato debolmente, ma non Java e C # ?.

Accidenti, questo è confuso inserisci qui la descrizione dell'immagine

Gli autori sembrano implicare che un linguaggio che impedisce l'applicazione di determinate operazioni su valori di diversi tipi sia fortemente tipizzato e che al contrario significhi debolmente digitato.

Pertanto, ad un certo punto mi sono sentito spinto a credere che se una lingua fornisce molte conversioni automatiche o la coercizione tra tipi (come perl) può finire per essere considerata debolmente tipizzata, mentre altre lingue che forniscono solo poche conversioni potrebbero finire per essere considerato fortemente tipizzato.

Sono propenso a credere, però, che devo sbagliarmi in questa interpretazione, semplicemente non so perché o come spiegarlo.

Quindi, le mie domande sono:

  • Cosa significa veramente che una lingua sia tipizzata in modo veramente debole?
  • Potresti citare qualche buon esempio di digitazione debole che non è correlato alla conversione automatica / coercizione automatica fatta dalla lingua?
  • Una lingua può essere tipizzata debolmente e fortemente tipizzata allo stesso tempo?

8
La tipizzazione forte contro quella debole riguarda la conversione del tipo (cos'altro potrebbe essere?) Se vuoi un esempio di un linguaggio "molto" debole, guarda questo: destroyallsoftware.com/talks/wat .
Wilduck,

2
@Wildduck Tutte le lingue forniscono conversioni di tipo, ma non tutte sono considerate di tipo debole. I miei esempi mostrati di seguito dimostrano come i programmatori considerano una lingua tipizzata debolmente sulla base degli stessi esempi possibili su altre lingue considerate fortemente tipizzate. Pertanto la mia domanda prevale ancora. Qual è la differenza?
Edwin Dalorzo,

1
La risposta breve, penso, è che "Typedness" non è uno stato binario. Java e C # sono più fortemente tipizzati ma non assolutamente.
Jodrell,

3
Credo che questo sia più adatto all'ingegneria del software .
zzzzBov

4
@Brendan Che ne dici di sommare un float e un numero intero? L'intero non è forzato in un float in Python? Diresti ora che Python non è assolutamente fortemente tipizzato?
Edwin Dalorzo,

Risposte:


210

AGGIORNAMENTO: Questa domanda è stata l'argomento del mio blog il 15 ottobre 2012. Grazie per l'ottima domanda!


Cosa significa veramente che una lingua sia "tipizzata debolmente"?

Significa "questo linguaggio utilizza un sistema di tipi che trovo sgradevole". Una lingua "fortemente tipizzata" al contrario è una lingua con un sistema di tipi che trovo piacevole.

I termini sono essenzialmente insignificanti e dovresti evitarli. Wikipedia elenca undici significati diversi per "fortemente tipizzato", molti dei quali sono contraddittori. Ciò indica che le probabilità di confusione create sono elevate in qualsiasi conversazione che implichi il termine "fortemente tipizzato" o "debolmente tipizzato".

Tutto ciò che puoi veramente dire con certezza è che un linguaggio "fortemente tipizzato" in discussione ha qualche restrizione aggiuntiva nel sistema dei tipi, sia in fase di esecuzione che in fase di compilazione, che manca un linguaggio "tipizzato in modo debole" in discussione. Quale potrebbe essere tale restrizione non può essere determinata senza ulteriore contesto.

Invece di usare "fortemente tipizzato" e "debolmente tipizzato", dovresti descrivere in dettaglio quale tipo di sicurezza del tipo intendi. Ad esempio, C # è un linguaggio tipicamente statico e un linguaggio sicuro di tipo e un linguaggio sicuro per la memoria , per la maggior parte. C # consente di violare tutte e tre queste forme di digitazione "forte". L'operatore cast viola la digitazione statica; dice al compilatore "So più del tipo di runtime di questa espressione di te". Se lo sviluppatore ha torto, il runtime genererà un'eccezione per proteggere la sicurezza dei tipi. Se lo sviluppatore desidera interrompere la sicurezza del tipo o della memoria, può farlo disattivando il sistema di sicurezza del tipo creando un blocco "non sicuro". In un blocco non sicuro puoi usare il puntatore magico per trattare un int come un float (violando la sicurezza del tipo) o per scrivere nella memoria che non possiedi. (Violare la sicurezza della memoria.)

C # impone restrizioni di tipo che vengono verificate sia in fase di compilazione che in fase di esecuzione, rendendola così una lingua "fortemente tipizzata" rispetto alle lingue che eseguono meno controlli in fase di compilazione o meno controlli in fase di esecuzione. C # consente anche in circostanze speciali di eseguire un end-run attorno a tali restrizioni, rendendolo una lingua "debolmente tipizzata" rispetto alle lingue che non consentono di eseguire tale end-run.

Che è davvero? È impossibile dirlo; dipende dal punto di vista di chi parla e dal suo atteggiamento nei confronti delle varie caratteristiche linguistiche.


14
@edalorzo: si basa sul gusto e sulle opinioni personali su (1) quali aspetti della teoria dei tipi sono rilevanti e quali sono irrilevanti, e (2) se è richiesto un linguaggio per far rispettare o semplicemente incoraggiare le restrizioni di tipo. Come ho sottolineato, si potrebbe ragionevolmente affermare che C # è fortemente tipizzato perché consente e incoraggia la tipizzazione statica, e si potrebbe altrettanto ragionevolmente dire che è tipizzato debolmente perché consente la possibilità di violare la sicurezza del tipo.
Eric Lippert,

4
@edalorzo: Anche per quanto riguarda il montaggio, è una questione di opinione. Un compilatore del linguaggio assembly non ti permetterà di spostare un doppio a 64 bit dallo stack in un registro a 32 bit; ti permetterà di spostare un puntatore a 32 bit in un doppio a 64 bit dallo stack in un registro a 32 bit. In questo senso il linguaggio è "typesafe" - impone una restrizione alla legalità del programma basata su una classificazione dei tipi di dati. Se tale restrizione sia "forte" o "debole" è una questione di opinione, ma è chiaramente una restrizione.
Eric Lippert,

2
Penso di vedere ora il tuo punto, un linguaggio tipicamente debolmente tipizzato dovrebbe essere totalmente non tipizzato o monotipato, cosa che nella vita reale è praticamente impossibile. Pertanto, qualsiasi lingua ha una certa definizione di tipi, che sono sicuri e, a seconda del numero di buchi che la lingua fornisce per violare o manipolare i suoi dati o tipi di dati, potresti finire per considerarlo più o meno debolmente digitato, forse anche in solo determinati contesti.
Edwin Dalorzo,

7
@edalorzo: Corretto. Ad esempio, il calcolo lambda non tipizzato è tipicamente meno tipicamente possibile. Ogni funzione è una funzione da una funzione a una funzione; tutti i dati possono essere passati a qualsiasi funzione senza restrizioni perché tutto è "dello stesso tipo". La validità di un'espressione nel calcolo lambda non tipizzato dipende solo dalla sua forma sintattica, non da un'analisi semantica che classifica alcune espressioni come aventi determinati tipi.
Eric Lippert,

3
@Mark Gli darei un altro +1 per prevedere che tutti avrebbero fornito interpretazioni diverse sull'argomento. Questo "carattere debole" sembra essere un "concetto mitico" o una "leggenda urbana", tutti l'hanno visto, ma nessuno può provare che esiste :-)
Edwin Dalorzo

64

Come altri hanno notato, i termini "fortemente tipizzato" e "debolmente tipizzato" hanno così tanti significati diversi che non esiste una sola risposta alla tua domanda. Tuttavia, dal momento che hai menzionato in modo specifico Perl nella tua domanda, lasciami provare a spiegare in che senso Perl è debolmente tipizzato.

Il punto è che, in Perl, non esiste una "variabile intera", una "variabile float", una "variabile stringa" o una "variabile booleana". Infatti, per quanto riguarda l'utente può (di solito) dire, non ci sono ancora integer, float, string o booleani valori : tutto quello che dovete sono "scalari", che sono tutte queste cose allo stesso tempo. Quindi, ad esempio, puoi scrivere:

$foo = "123" + "456";           # $foo = 579
$bar = substr($foo, 2, 1);      # $bar = 9
$bar .= " lives";               # $bar = "9 lives"
$foo -= $bar;                   # $foo = 579 - 9 = 570

Naturalmente, come noterai correttamente, tutto ciò può essere visto come solo la coercizione del tipo. Ma il punto è che, in Perl, i tipi sono sempre costretti. In effetti, è abbastanza difficile per un utente dire quale potrebbe essere il "tipo" interno di una variabile: alla riga 2 nel mio esempio sopra, chiedendo se il valore di $barè la stringa "9"o il numero 9è praticamente insignificante, poiché, come per quanto riguarda Perl, quelli sono la stessa cosa . In effetti, è anche possibile che uno scalare Perl abbia internamente sia una stringa che un valore numerico allo stesso tempo, come ad esempio nel caso della $fooseconda riga successiva.

Il rovescio della medaglia di tutto ciò è che, poiché le variabili Perl sono non tipizzate (o, piuttosto, non espongono il loro tipo interno all'utente), gli operatori non possono essere sovraccaricati per fare cose diverse per diversi tipi di argomenti; non puoi semplicemente dire "questo operatore farà X per i numeri e Y per le stringhe", perché l'operatore non può (non) dire che tipo di valori sono i suoi argomenti.

Quindi, per esempio, Perl ha e necessita sia di un operatore di addizione numerica ( +) che di un operatore di concatenazione di stringhe ( .): come hai visto sopra, è perfettamente corretto aggiungere stringhe ( "1" + "2" == "3") o concatenare numeri ( 1 . 2 == 12). Analogamente, gli operatori di confronto numerico ==, !=, <, >, <=, >=e <=>confrontare i valori numerici dei loro argomenti, mentre gli operatori di confronto stringa eq, ne, lt, gt, le, gee cmpconfrontarli lexicographically come stringhe. Quindi 2 < 10, ma 2 gt 10(ma "02" lt 10, mentre "02" == 2). (Intendiamoci, alcune altre lingue, come JavaScript, provano ad accettare la digitazione debole simile al Perl mentrefacendo anche sovraccarico dell'operatore. Questo porta spesso alla bruttezza, come la perdita di associatività per +.)

(La mosca dell'unguento qui è che, per ragioni storiche, Perl 5 ha alcuni casi angolari, come gli operatori logici bit a bit, il cui comportamento dipende dalla rappresentazione interna dei loro argomenti. Questi sono generalmente considerati un fastidioso difetto di progettazione, poiché la rappresentazione interna può cambiare per ragioni sorprendenti, e quindi prevedere esattamente cosa fanno quegli operatori in una data situazione può essere complicato.)

Detto questo, si potrebbe sostenere che Perl ha avere tipi forti; non sono solo il tipo di tipi che potresti aspettarti. In particolare, oltre al tipo "scalare" discusso sopra, Perl ha anche due tipi strutturati: "array" e "hash". Questi sono molto distinti dagli scalari, al punto in cui le variabili Perl hanno sigilli diversi che indicano il loro tipo ( $per scalari, @array, %hash) 1 . Ci sono regole di coercizione tra questi tipi, in modo da poter scrivere ad esempio %foo = @bar, ma molti di loro sono abbastanza lossy: per esempio, $foo = @barassegna la lunghezza dell'array @bara$foo, non il suo contenuto. (Inoltre, ci sono alcuni altri tipi strani, come typeglobs e handle I / O, che non si vedono spesso esposti.)

Inoltre, una leggera differenza in questo bel design è l'esistenza di tipi di riferimento, che sono un tipo speciale di scalari (e che possono essere distinti dai normali scalari, usando l' refoperatore). È possibile usare i riferimenti come normali scalari, ma i loro valori stringa / numerici non sono particolarmente utili e tendono a perdere il loro riferimento speciale se li modificate usando le normali operazioni scalari. Inoltre, qualsiasi variabile Perl 2 può essere blessedita in una classe, trasformandola in un oggetto di quella classe; il sistema di classe OO in Perl è in qualche modo ortogonale al sistema di tipo primitivo (o tipicità) sopra descritto, anche se è anche "debole" nel senso di seguire la tipizzazione anatraparadigma. L'opinione generale è che, se ti ritrovi a controllare la classe di un oggetto in Perl, stai facendo qualcosa di sbagliato.


1 In realtà, il sigillo indica il tipo del valore a cui si accede, in modo che, ad esempio il primo scalare nella matrice @fooè indicata $foo[0]. Vedi perlfaq4 per maggiori dettagli.

2 Gli oggetti in Perl sono (normalmente) accessibili tramite riferimenti ad essi, ma ciò che effettivamente viene modificato blessè la variabile (possibilmente anonima) a cui punta il riferimento. Tuttavia, la benedizione è davvero una proprietà della variabile, non del suo valore, quindi ad esempio l'assegnare la variabile benedetta effettiva a un'altra ti dà una copia superficiale e senza macchia di essa. Vedi perlobj per maggiori dettagli.


19

Oltre a ciò che Eric ha detto, considera il seguente codice C:

void f(void* x);

f(42);
f("hello");

Contrariamente a linguaggi come Python, C #, Java o quant'altro, quanto sopra viene digitato debolmente perché perdiamo le informazioni sul tipo. Eric ha giustamente sottolineato che in C # possiamo eludere il compilatore eseguendo il casting, dicendo efficacemente "So di più sul tipo di questa variabile di te".

Ma anche in questo caso, il runtime controllerà comunque il tipo! Se il cast non è valido, il sistema di runtime lo catturerà e genererà un'eccezione.

Con la cancellazione del tipo, ciò non accade: le informazioni sul tipo vengono eliminate. Un cast void*in C fa esattamente questo. A questo proposito, quanto sopra è sostanzialmente diverso da una dichiarazione del metodo C # come void f(Object x).

(Tecnicamente, C # consente anche la cancellazione del tipo tramite codice non sicuro o marshalling.)

Questo è tipizzato debolmente come si arriva. Tutto il resto è solo una questione di controllo statico vs. dinamico del tipo, cioè del momento in cui un tipo viene controllato.


1
+1 Bene, ora mi hai fatto pensare alla cancellazione del tipo come a una caratteristica che può anche implicare una "tipizzazione debole". Esiste anche la cancellazione dei tipi in Java e, in fase di esecuzione, il sistema dei tipi consente di violare i vincoli che il compilatore non approverebbe mai. L'esempio C è eccellente per illustrare il punto.
Edwin Dalorzo,

1
D'accordo, ci sono strati per la cipolla o l'inferno. Sembra una definizione più significativa di debolezza del tipo.
Jodrell,

1
@edalorzo Non penso che questo sia abbastanza lo stesso poiché anche se Java ti consente di aggirare il compilatore, il sistema del tipo di runtime rileverà comunque la violazione. Quindi il sistema di tipi di runtime Java è fortemente tipizzato a questo proposito (ci sono eccezioni, ad esempio dove la riflessione può essere usata per aggirare il controllo di accesso).
Konrad Rudolph,

1
@edalorzo Puoi solo aggirare il compilatore in questo modo, non il sistema di runtime. È importante rendersi conto che linguaggi come Java e C # (e in una certa misura anche C ++) hanno un sistema di tipi che è garantito due volte: una volta in fase di compilazione e una volta in fase di esecuzione. void*supera entrambi i controlli di tipo. La cancellazione generica del tipo no, aggira solo i controlli in fase di compilazione. È esattamente come i cast espliciti (menzionati da Eric) a questo proposito.
Konrad Rudolph,

1
@edalorzo Re la tua confusione: non dovremmo. La distinzione è fluente. E sì, la cancellazione del tipo rende Java debolmente digitato in questo senso. Il mio punto era che anche con la cancellazione di tipo generico non è ancora possibile eludere i controlli del tipo di runtime se non si utilizza anche la riflessione .
Konrad Rudolph,

14

Un esempio perfetto viene dall'articolo di Wikipedia di Strong Typing :

La tipizzazione generalmente forte implica che il linguaggio di programmazione pone gravi restrizioni al mixaggio che può avvenire.

Digitazione debole

a = 2
b = "2"

concatenate(a, b) # returns "22"
add(a, b) # returns 4

Digitazione forte

a = 2
b = "2"

concatenate(a, b) # Type Error
add(a, b) # Type Error
concatenate(str(a), b) #Returns "22"
add(a, int(b)) # Returns 4

Si noti che un linguaggio di digitazione debole può mescolare tipi diversi senza errori. Un linguaggio di tipo forte richiede che i tipi di input siano i tipi previsti. In un linguaggio di tipo forte un tipo può essere convertito ( str(a)converte un numero intero in una stringa) o cast ( int(b)).

Tutto dipende dall'interpretazione della digitazione.


3
Ma questo porta agli esempi contraddittori forniti nella domanda. Un linguaggio fortemente tipizzato può includere una coercizione implicita che significa che uno o entrambi i tuoi due esempi "Errore di tipo" vengono convertiti automaticamente nel pertinente dei secondi due esempi, ma generalmente quel linguaggio è ancora fortemente tipizzato.
Mark Hurd,

3
Vero. Immagino che potresti dire che ci sono vari gradi di digitazione forte e digitazione debole. La conversione implicita potrebbe significare che la lingua è meno tipizzata rispetto a una lingua che non esegue una conversione implicita.
SaulBack,

4

Vorrei contribuire alla discussione con le mie ricerche sull'argomento, mentre altri commentano e contribuiscono, ho letto le loro risposte e seguito i loro riferimenti e ho trovato informazioni interessanti. Come suggerito, è probabile che gran parte di questo sarebbe meglio discusso nel forum dei programmatori, poiché sembra essere più teorico che pratico.

Da un punto di vista teorico, penso che l'articolo di Luca Cardelli e Peter Wegner intitolato On Understanding Tipi, Data Abstraction e Polymorphism abbia uno dei migliori argomenti che ho letto.

Un tipo può essere visto come un set di vestiti (o un'armatura) che protegge una rappresentazione non tipizzata sottostante da un uso arbitrario o non intenzionale. Fornisce una copertura protettiva che nasconde la rappresentazione sottostante e limita il modo in cui gli oggetti possono interagire con altri oggetti. In un sistema non tipizzato, gli oggetti non tipizzati sono nudi in quanto la rappresentazione sottostante è esposta da tutti. La violazione del sistema dei tipi comporta la rimozione del set protettivo di indumenti e il funzionamento diretto sulla rappresentazione nuda.

Questa affermazione sembra suggerire che una digitazione debole ci consentirebbe di accedere alla struttura interna di un tipo e di manipolarla come se fosse qualcos'altro (un altro tipo). Forse quello che potremmo fare con un codice non sicuro (menzionato da Eric) o con i puntatori cancellati dal tipo c menzionati da Konrad.

L'articolo continua ...

Le lingue in cui tutte le espressioni sono coerenti con i tipi sono chiamate linguaggi fortemente tipizzati. Se una lingua è fortemente tipizzata, il suo compilatore può garantire che i programmi che accetta verranno eseguiti senza errori di tipo. In generale, dovremmo cercare di digitare forte e adottare la scrittura statica quando possibile. Nota che ogni linguaggio tipizzato staticamente è fortemente tipizzato ma il contrario non è necessariamente vero.

In quanto tale, la tipizzazione forte significa l'assenza di errori di tipo, posso solo supporre che la digitazione debole significhi il contrario: la probabile presenza di errori di tipo. In fase di runtime o compilazione? Sembra irrilevante qui.

Cosa divertente, secondo questa definizione, un linguaggio con potenti coercizioni di tipo come il Perl sarebbe considerato fortemente tipizzato, perché il sistema non sta fallendo, ma sta affrontando i tipi costringendoli in equivalenze appropriate e ben definite.

D'altra parte, potrei dire che l'indennità di ClassCastExceptione ArrayStoreException(in Java) e InvalidCastException, ArrayTypeMismatchException(in C #) indicherebbe un livello di digitazione debole, almeno in fase di compilazione? La risposta di Eric sembra essere d'accordo con questo.

In un secondo articolo chiamato Typeful Programming fornito in uno dei riferimenti forniti in una delle risposte a questa domanda, Luca Cardelli approfondisce il concetto di violazioni del tipo:

La maggior parte dei linguaggi di programmazione del sistema consente violazioni arbitrarie del tipo, alcune in modo indiscriminato, altre solo in parti ristrette di un programma. Le operazioni che comportano violazioni dei tipi sono chiamate non corrette. Le violazioni dei tipi rientrano in diverse classi [tra cui possiamo menzionare]:

Coercizioni di valori di base : includono conversioni tra numeri interi, valori booleani, caratteri, insiemi, ecc. In questo caso non sono necessarie violazioni dei tipi, poiché è possibile fornire interfacce incorporate per eseguire le coercizioni in modo sonoro.

Pertanto, le coercizioni dei tipi come quelle fornite dagli operatori potrebbero essere considerate violazioni dei tipi, ma se non interrompono la coerenza del sistema dei tipi, potremmo dire che non portano a un sistema debolmente tipizzato.

Sulla base di questo né Python, Perl, Java o C # sono tipizzati debolmente.

Cardelli menziona due vilazioni di tipo che considero molto bene i casi di tipizzazione veramente debole:

Indirizzo aritmetico. Se necessario, dovrebbe esserci un'interfaccia incorporata (non corretta), che fornisce le operazioni adeguate su indirizzi e conversioni di tipo. Varie situazioni coinvolgono puntatori nell'heap (molto pericoloso con il trasferimento di raccoglitori), puntatori allo stack, puntatori ad aree statiche e puntatori ad altri spazi di indirizzi. A volte l'indicizzazione dell'array può sostituire l'aritmetica degli indirizzi. Mappatura della memoria. Ciò implica considerare un'area di memoria come una matrice non strutturata, sebbene contenga dati strutturati. Questo è tipico degli allocatori di memoria e dei raccoglitori.

Questo tipo di cose possibili in linguaggi come C (citato da Konrad) o attraverso un codice non sicuro in .Net (citato da Eric) implicherebbe davvero una digitazione debole.

Credo che la risposta migliore finora sia quella di Eric, perché la definizione di questi concetti è molto teorica, e quando si tratta di un linguaggio particolare, le interpretazioni di tutti questi concetti possono portare a conclusioni discutibili diverse.


4

Una digitazione debole significa in effetti che un'alta percentuale di tipi può essere implicitamente forzata, tentando di indovinare ciò che intendeva il programmatore.

La tipizzazione forte significa che i tipi non sono costretti, o almeno costretti meno.

La digitazione statica significa che i tipi delle variabili vengono determinati al momento della compilazione.

Molte persone hanno recentemente confuso "tipicamente manifestato" con "fortemente tipizzato". "Digitato manifestamente" significa che dichiari esplicitamente i tipi di variabili.

Python è tipicamente fortemente tipizzato, anche se puoi usare quasi tutto in un contesto booleano, e i booleani possono essere usati in un contesto intero e puoi usare un numero intero in un contesto float. Non è tipizzato in modo evidente, perché non è necessario dichiarare i tuoi tipi (tranne Cython, che non è del tutto pitone, sebbene interessante). Inoltre non è tipicamente statico.

C e C ++ sono manifestati tipicamente, digitati staticamente e in qualche modo fortemente tipizzati, perché dichiari i tuoi tipi, i tipi sono determinati in fase di compilazione e puoi mescolare numeri interi e puntatori, o numeri interi e doppi, o persino lanciare un puntatore su un tipo in un puntatore a un altro tipo.

Haskell è un esempio interessante, perché non è tipicamente digitato, ma è anche staticamente e fortemente tipizzato.


+1 Perché mi piace il termine coniato "manifestato tipicamente", che classifica i linguaggi come Java e C # in cui devi dichiarare esplicitamente i tipi e distinguerli da altri linguaggi di tipo statico come Haskell e Scala in cui l'inferenza del tipo svolge un ruolo importante e questo in genere confonde le persone, come dici tu, e fa credere loro che queste lingue siano tipizzate in modo dinamico.
Edwin Dalorzo,

3

La tipizzazione forte <=> non riguarda solo il continuum su quanto o quanto poco dei valori vengono forzati automaticamente dalla lingua per un tipo di dati a un altro, ma quanto fortemente o debolmente vengono digitati i valori reali . In Python e Java, e principalmente in C #, i valori hanno i loro tipi impostati in pietra. In Perl, non tanto - ci sono davvero solo una manciata di tipi di valore diversi da archiviare in una variabile.

Apriamo i casi uno per uno.


Pitone

Nell'esempio di Python 1 + "1", l' +operatore chiama il __add__tipo for intassegnandogli la stringa "1"come argomento, tuttavia ciò comporta NotImplemented:

>>> (1).__add__('1')
NotImplemented

Quindi, l'interprete prova a __radd__usare str:

>>> '1'.__radd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__radd__'

Se fallisce, l' +operatore fallisce con il risultato TypeError: unsupported operand type(s) for +: 'int' and 'str'. In quanto tale, l'eccezione non dice molto sulla tipizzazione forte, ma il fatto che l'operatore + non costringa i suoi argomenti automaticamente allo stesso tipo, è un puntatore al fatto che Python non è il linguaggio più debolmente tipizzato nel continuum.

D'altra parte, in Python 'a' * 5 è implementato:

>>> 'a' * 5
'aaaaa'

Questo è,

>>> 'a'.__mul__(5)
'aaaaa'

Il fatto che l'operazione sia diversa richiede una tipizzazione forte, tuttavia il contrario di *forzare i valori in numeri prima di moltiplicarli non renderebbe necessariamente i valori debolmente digitati.


Giava

L'esempio Java String result = "1" + 1;funziona solo perché, per comodità, l'operatore +è sovraccarico di stringhe. L' +operatore Java sostituisce la sequenza con la creazione di un StringBuilder(vedi questo ):

String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()

Questo è piuttosto un esempio di tipizzazione molto statica, senza alcuna reale coercizione - StringBuilderha un metodo append(Object)che è specificamente usato qui. La documentazione dice quanto segue:

Aggiunge la rappresentazione in formato stringa Objectdell'argomento.

L'effetto complessivo è esattamente come se l'argomento fosse convertito in una stringa dal metodo String.valueOf(Object)e i caratteri di quella stringa fossero quindi aggiunti a questa sequenza di caratteri.

Dove String.valueOfallora

Restituisce la rappresentazione in formato stringa dell'argomento Object. [Restituisce] se l'argomento è null, quindi una stringa uguale a "null"; in caso contrario, obj.toString()viene restituito il valore di .

Quindi questo è un caso di assoluta coercizione da parte del linguaggio, delegando ogni preoccupazione agli oggetti stessi.


C #

Secondo la risposta di Jon Skeet qui , l'operatore +non è nemmeno sovraccarico per la stringclasse - simile a Java, questa è solo la comodità generata dal compilatore, grazie sia alla tipizzazione statica che a quella forte.


Perl

Come spiega la perldata ,

Perl ha tre tipi di dati integrati: scalari, array di scalari e array associativi di scalari, noti come "hash". Uno scalare è una singola stringa (di qualsiasi dimensione, limitata solo dalla memoria disponibile), un numero o un riferimento a qualcosa (che verrà discusso in perlref). Le matrici normali sono elenchi ordinati di scalari indicizzati per numero, che iniziano con 0. Gli hash sono raccolte non ordinate di valori scalari indicizzati dalla chiave di stringa associata.

Perl tuttavia non ha un tipo di dati separato per numeri, valori booleani, stringhe, valori null, undefineds, riferimenti ad altri oggetti ecc. - ha solo un tipo per tutti questi, il tipo scalare; 0 è un valore scalare tanto quanto "0". Una variabile scalare impostata come stringa può davvero trasformarsi in un numero e da lì in poi si comporta diversamente da "solo una stringa", se si accede in un contesto numerico. Lo scalare può contenere qualsiasi cosa in Perl, è tanto l'oggetto quanto esiste nel sistema. mentre in Python i nomi si riferiscono solo agli oggetti, in Perl i valori scalari nei nomi sono oggetti modificabili. Inoltre, il sistema del tipo orientato agli oggetti è incollato sopra a questo: ci sono solo 3 tipi di dati in perl - scalari, elenchi e hash. Un oggetto definito dall'utente in Perl è un riferimento (che è un puntatore a uno dei 3 precedenti) blessed a un pacchetto - puoi prendere qualsiasi valore e benedirlo a qualsiasi classe in qualsiasi momento tu voglia.

Perl ti consente anche di modificare le classi di valori per capriccio - questo non è possibile in Python dove per creare un valore di una classe è necessario costruire esplicitamente il valore appartenente a quella classe con object.__new__o simili. In Python non puoi davvero cambiare l'essenza dell'oggetto dopo la creazione, in Perl puoi fare qualsiasi cosa:

package Foo;
package Bar;

my $val = 42;
# $val is now a scalar value set from double
bless \$val, Foo;
# all references to $val now belong to class Foo
my $obj = \$val;
# now $obj refers to the SV stored in $val
# thus this prints: Foo=SCALAR(0x1c7d8c8)
print \$val, "\n"; 
# all references to $val now belong to class Bar
bless \$val, Bar;
# thus this prints Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# we change the value stored in $val from number to a string
$val = 'abc';
# yet still the SV is blessed: Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# and on the course, the $obj now refers to a "Bar" even though
# at the time of copying it did refer to a "Foo".
print $obj, "\n";

quindi l'identità del tipo è debolmente legata alla variabile e può essere cambiata attraverso qualsiasi riferimento al volo. In effetti, se lo fai

my $another = $val;

\$anothernon ha l'identità di classe, anche se \$valfornirà comunque il riferimento benedetto.


TL; DR

Ci sono molte più cose sulla tipizzazione debole in Perl che non solo le coercizioni automatiche, ed è più che i tipi dei valori stessi non sono messi in pietra, a differenza del Python che è un linguaggio tipizzato in modo dinamico ma molto fortemente tipizzato. Che Python dà TypeErrorsu 1 + "1"è un'indicazione che la lingua è fortemente tipizzato, anche se il contrario di fare qualcosa di utile, come in Java o C # non preclude loro di essere fortemente tipizzati lingue.


Questo è totalmente confuso. Che le variabili Perl 5 non abbiano tipi non ha alcun valore sui valori , che hanno sempre un tipo.
Jim Balter,

@JimBalter bene, sì, un valore ha un tipo in quanto è una stringa o un numero e può comportarsi in modo diverso in alcuni contesti a seconda che la variabile scalare contenga una stringa o un numero; ma il valore contenuto in una variabile può cambiare tipo semplicemente accedendo alla variabile e poiché il valore stesso vive nella variabile, i valori stessi possono essere considerati mutabili tra i tipi.
Antti Haapala,

I valori non cambiano i tipi - è incoerente; un valore è sempre di un tipo . Il valore contenuto in una variabile può cambiare. Una modifica da 1 a "1" è tanto una variazione di valore quanto una variazione da 1 a 2.
Jim Balter,

Un linguaggio debolmente tipizzato come Perl consente al precedente tipo di modifica del valore di verificarsi implicitamente a seconda del contesto. Ma anche C ++ consente conversioni così implicite tramite le definizioni dell'operatore. La digitazione debole è una proprietà molto informale e in realtà non è un modo utile per descrivere le lingue, come ha sottolineato Eric Lippert.
Jim Balter,

PS Si può dimostrare che, anche in Perl, <digits> e "<digits>" hanno valori diversi, non solo tipi diversi. Perl fa apparire <cifre> e "<cifre>" nella maggior parte dei casi sembrano avere lo stesso valore tramite conversioni implicite , ma l'illusione non è completa; ad es. "12" | "34" è 36 mentre 12 | 34 è 46. Un altro esempio è che "00" è numericamente uguale a 00 nella maggior parte dei contesti, ma non nel contesto booleano, dove "00" è vero ma 00 è falso.
Jim Balter,

1

Come molti altri hanno espresso, l'intera nozione di "forte" vs "debole" è problematica.

Come archetipo, Smalltalk è fortemente tipizzato - solleverà sempre un'eccezione se un'operazione tra due oggetti è incompatibile. Tuttavia, sospetto che pochi in questo elenco chiamerebbero Smalltalk un linguaggio fortemente tipizzato, poiché digitato in modo dinamico.

Trovo che la nozione di "statica" rispetto a "dinamica" sia più utile di "forte" rispetto a "debole". Un linguaggio tipizzato staticamente ha tutti i tipi individuati in fase di compilazione e il programmatore deve dichiarare esplicitamente se diversamente.

In contrasto con un linguaggio tipizzato in modo dinamico, in cui la digitazione viene eseguita in fase di esecuzione. Questo è in genere un requisito per i linguaggi polimorfici, quindi le decisioni sulla legittimità di un'operazione tra due oggetti non devono essere decise dal programmatore in anticipo.

Nei linguaggi polimorfici e tipicamente dinamici (come Smalltalk e Ruby), è più utile pensare a un "tipo" come a "conformità al protocollo". Se un oggetto obbedisce a un protocollo allo stesso modo di un altro oggetto - anche se i due oggetti non condividono alcuna eredità o mixin o altro voodoo - sono considerati lo stesso "tipo" dal sistema di runtime. Più correttamente, un oggetto in tali sistemi è autonomo e può decidere se ha senso rispondere a un particolare messaggio facendo riferimento a un particolare argomento.

Desideri un oggetto in grado di fornire una risposta significativa al messaggio "+" con un argomento oggetto che descriva il colore blu? Puoi farlo in linguaggi di tipo dinamico, ma è un dolore in linguaggi di tipo statico.


3
Penso che il concetto di tipizzazione dinamica vs statica non sia in discussione. Anche se devo dire che non credo che il polimorfismo sia comunque svantaggiato in linguaggi di tipo statico. In definitiva, il sistema di tipi verifica se una determinata operazione è applicabile a determinati operandi, sia in fase di esecuzione che in fase di compilazione. Inoltre, altre forme di polimorfismo, come le funzioni parametriche e le classi, consentono di combinare i tipi in linguaggi di tipo statico in modi che hai descritto come molto difficili se confrontati con quelli tipizzati dinamicamente, anche meglio se l'inferenza del tipo è fornita.
Edwin Dalorzo,

0

Mi piace la risposta di @Eric Lippert , ma per rispondere alla domanda - i linguaggi fortemente tipizzati in genere hanno una conoscenza esplicita dei tipi di variabili in ogni punto del programma. Le lingue tipicamente deboli non lo fanno, quindi possono tentare di eseguire un'operazione che potrebbe non essere possibile per un determinato tipo. Pensa che il modo più semplice per vederlo sia in una funzione. C ++:

void func(string a) {...}

aÈ noto che la variabile è di tipo stringa e qualsiasi operazione incompatibile verrà rilevata al momento della compilazione.

Pitone:

def func(a)
  ...

La variabile apotrebbe essere qualsiasi cosa e possiamo avere un codice che chiama un metodo non valido, che verrà catturato solo in fase di esecuzione.


12
Penso che potresti confondere la digitazione dinamica con la digitazione statica con la digitazione forte contro la digitazione debole. In entrambe le versioni del codice, i sistemi di tipo runtime sanno benissimo che a è una stringa. È solo che nel primo caso, il compilatore può dirlo, nel secondo non può. Ma questo non rende nessuna di queste lingue tipicamente debole.
Edwin Dalorzo,
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.