Jaka jest różnica między językiem silnie typowanym a statycznym?


Odpowiedzi:


542

Jaka jest różnica między językiem silnie typowanym a statycznym?

Język o typie statycznym ma system typów sprawdzany w czasie kompilacji przez implementację (kompilator lub interpreter). Kontrola typu odrzuca niektóre programy, a programy, które pomyślnie przejdą kontrolę, zazwyczaj mają pewne gwarancje; na przykład kompilator gwarantuje, że nie będzie stosować liczbowych instrukcji arytmetycznych na liczbach zmiennoprzecinkowych.

Nie ma prawdziwej zgody co do tego, co oznacza „mocno wpisany na maszynie”, chociaż najczęściej stosowana definicja w literaturze fachowej jest taka, że ​​w języku „silnie wpisanego na maszynie” programista nie może obejść ograniczeń narzuconych przez system typów . Termin ten prawie zawsze używany jest do opisu języków typowanych statycznie.

Statyczny vs dynamiczny

Przeciwieństwem typowania statycznego jest „typowanie dynamiczne”, co oznacza, że

  1. Wartości używane w czasie wykonywania są podzielone na typy.
  2. Istnieją ograniczenia dotyczące używania takich wartości.
  3. W przypadku naruszenia tych ograniczeń naruszenie jest zgłaszane jako błąd typu (dynamiczny).

Na przykład Lua , język o typie dynamicznym, ma między innymi typ łańcucha, typ liczby i typ boolowski. W Lua każda wartość należy do dokładnie jednego typu, ale nie jest to wymagane dla wszystkich języków z dynamicznym pisaniem. W Lua dozwolone jest łączenie dwóch łańcuchów, ale niedozwolone jest łączenie łańcucha i wartości logicznej.

Silny kontra słaby

Przeciwieństwem „silnie typowanego” jest „słabo typowany”, co oznacza, że ​​możesz obejść system pisma. C jest notorycznie słabo typowane, ponieważ każdy typ wskaźnika można przekształcić na dowolny inny typ wskaźnika po prostu przez rzutowanie. Pascal miał być mocno napisany na maszynie, ale niedopatrzenie w projekcie (nieoznakowane zapisy wariantów) wprowadziło lukę w systemie typów, więc technicznie jest on słabo napisany. Przykłady naprawdę silnie napisanych języków obejmują CLU, Standard ML i Haskell. Standard ML został w rzeczywistości poddany kilku zmianom w celu usunięcia luk w systemie typów, które zostały odkryte po szerokim wdrożeniu języka.

Co się tu naprawdę dzieje?

Ogólnie rzecz biorąc, okazuje się, że mówienie o „silnych” i „słabych” nie jest zbyt przydatne. To, czy system typów ma lukę, jest mniej ważne niż dokładna liczba i charakter luk, jak prawdopodobne jest ich pojawienie się w praktyce i jakie są konsekwencje wykorzystania luki. W praktyce najlepiej jest unikać słów „silny” i „słaby” , ponieważ

  • Amatorzy często łączą je z „statycznymi” i „dynamicznymi”.

  • Najwyraźniej „słabe pisanie” jest używane przez niektóre osoby do mówienia o względnej częstości występowania lub braku niejawnych konwersji.

  • Specjaliści nie mogą dokładnie ustalić, co oznaczają te warunki.

  • Ogólnie rzecz biorąc, jest mało prawdopodobne, aby poinformować lub oświecić odbiorców.

Smutna prawda jest taka, że ​​jeśli chodzi o systemy typu, „mocne” i „słabe” nie mają powszechnie uzgodnionego znaczenia technicznego. Jeśli chcesz omówić względną siłę systemów typu, lepiej omówić dokładnie, jakie gwarancje są i nie są zapewniane. Na przykład dobrym pytaniem jest: „czy gwarantuje się, że każda wartość danego typu (lub klasy) została stworzona przez wywołanie jednego z konstruktorów tego typu?” W C odpowiedź brzmi „nie”. W CLU, F # i Haskell jest tak. W przypadku C ++ nie jestem pewien - chciałbym wiedzieć.

Natomiast wpisywanie statyczne oznacza, że ​​programy są sprawdzane przed uruchomieniem, a program może zostać odrzucony przed jego uruchomieniem. Pisanie dynamiczne oznacza, że ​​typy wartości są sprawdzane podczas wykonywania, a źle wpisana operacja może spowodować zatrzymanie programu lub inny sygnał błędu w czasie wykonywania. Podstawowym powodem pisania statycznego jest wykluczenie programów, które mogą mieć takie „błędy typu dynamicznego”.

Czy jedno implikuje drugie?

Na poziomie pedantycznym nie, ponieważ słowo „silny” tak naprawdę nic nie znaczy. Ale w praktyce ludzie prawie zawsze robią jedną z dwóch rzeczy:

  • (Niepoprawnie) używają „silnego” i „słabego”, aby oznaczać „statyczny” i „dynamiczny”, w którym to przypadku (niepoprawnie) używają zamiennie „silnie typowanego” i „statycznie typowanego”.

  • Używają „silnych” i „słabych” do porównywania właściwości systemów typu statycznego. Bardzo rzadko można usłyszeć, jak ktoś mówi o „silnym” lub „słabym” systemie dynamicznym. Z wyjątkiem FORTH, który tak naprawdę nie ma żadnego systemu pisma, nie mogę wymyślić dynamicznie piszącego języka, w którym system pisma mógłby zostać obrócony. W pewnym sensie kontrole te są wbudowywane w silnik wykonawczy, a każda operacja jest sprawdzana pod kątem bezpieczeństwa przed wykonaniem.

Tak czy inaczej, jeśli dana osoba nazywa język „silnie wpisanym”, najprawdopodobniej będzie mówić o języku wpisywanym statycznie.


3
@Adam: Najwyraźniej niewystarczająco poprawne, aby zostać wyróżnionym :) Ponieważ odpowiedź Cletusa zawiera tak wiele nieporozumień (chociaż zredagowałam najgorsze z nich), poczułam się zmuszona przeliterować wszystko słowami jednej sylaby ...
Norman Ramsey

1
Cóż, głosowałem cię :) Nawet słowo „kompiluj” nie jest jasne w dzisiejszych maszynach wirtualnych z dynamicznymi językami. Technicznie Java i C # są kompilowane dwukrotnie (JIT) i oba przeprowadzają analizę typów. Język taki jak Javascript działający w .NET vm może być bardziej bezpieczny Typesafe z powodu maszyny wirtualnej.
Adam Gent

2
Jestem teraz taki zmieszany! Okay, drodzy wielcy gladiatorzy areny, czy biedna dusza taka jak ja może pójść z następującym prostym zrozumieniem? 1.Static: Wartości są powiązane z typem w czasie kompilacji, a nie w czasie wykonywania. 2. Dynamiczne: Wartości są powiązane z typem w czasie wykonywania, dlatego typ wartości może się zmieniać w czasie wykonywania ~, więc jest bardziej podatny na problemy związane z rzutowaniem typu podczas działania. 3. Mocny / słaby: zapomnij! To nie są terminy techniczne i po prostu zła nomenklatura. To zależy od kontekstu, o którym się mówi. Czy mogę kontynuować życie z tym prostym zrozumieniem? :(
Saurabh Patil

„czy gwarantuje się, że każda wartość danego typu (lub klasy) została utworzona przez wywołanie jednego z konstruktorów tego typu?” W C odpowiedź brzmi „nie”. Czy ktoś mógłby podać przykładową sytuację, w której ma to miejsce w C? Przypuszczam, że wiąże się to z rzutowaniem wskaźników na struktury?
corazza

Mocne i słabe pisanie: brak takiej klasyfikacji.
Raúl

248

Jest to często źle rozumiane, więc wyjaśnię to.

Pisanie statyczne / dynamiczne

Wpisywanie statyczne to miejsce, w którym typ jest powiązany ze zmienną . Typy są sprawdzane podczas kompilacji.

Wpisywanie dynamiczne polega na powiązaniu typu z wartością . Typy są sprawdzane w czasie wykonywania.

Na przykład w Javie:

String s = "abcd";

s„na zawsze” będzie String. Podczas swojego życia może wskazywać na różne Strings (ponieważ sjest odniesieniem w Javie). Może mieć nullwartość, ale nigdy nie będzie się odnosić do Integerani List. To jest pisanie statyczne.

W PHP:

$s = "abcd";          // $s is a string
$s = 123;             // $s is now an integer
$s = array(1, 2, 3);  // $s is now an array
$s = new DOMDocument; // $s is an instance of the DOMDocument class

To dynamiczne pisanie.

Mocne / słabe pisanie

(Edytuj alert!)

Silne pisanie na maszynie to fraza bez powszechnie uzgodnionego znaczenia. Większość programistów, którzy używają tego terminu w znaczeniu innego niż pisanie statyczne, używa go, aby sugerować, że istnieje dyscyplina typów wymuszona przez kompilator. Na przykład CLU ma silny system typów, który nie pozwala kodowi klienta na tworzenie wartości typu abstrakcyjnego, chyba że za pomocą konstruktorów dostarczonych przez ten typ. C ma dość silny system typów, ale można go do pewnego stopnia „obalić”, ponieważ program zawsze może rzutować wartość jednego typu wskaźnika na wartość innego typu wskaźnika. Na przykład w C możesz wziąć wartość zwróconą przez malloc()i z radością ją rzucić FILE*, a kompilator nie będzie próbował cię zatrzymać - ani nawet ostrzec, że robisz coś podejrzanego.

(Oryginalna odpowiedź mówiła coś o wartości „nie zmieniającej typu w czasie wykonywania”. Znałem wielu projektantów języków i autorów kompilatorów i nie znałem takiej, która mówiła o wartościach zmieniających typ w czasie wykonywania, z wyjątkiem ewentualnie bardzo zaawansowanych badań typu systemy, w których jest to znane jako „problem silnej aktualizacji”).

Słabe pisanie oznacza, że ​​kompilator nie wymusza dyskietki pisania, a może wymuszenie można łatwo obalić.

Oryginał tej odpowiedzi łączył słabe pisanie z niejawną konwersją (czasami nazywaną także „niejawną promocją”). Na przykład w Javie:

String s = "abc" + 123; // "abc123";

Ten kod jest przykładem niejawnej promocji: 123 jest domyślnie konwertowany na ciąg znaków przed połączeniem "abc". Można argumentować, że kompilator Java przepisuje ten kod jako:

String s = "abc" + new Integer(123).toString();

Rozważ klasyczny problem PHP „zaczyna się od”:

if (strpos('abcdef', 'abc') == false) {
  // not found
}

Błąd polega na tym, że strpos()zwraca indeks dopasowania, ponieważ 0. 0 jest wymuszane na wartość logiczną, falsea zatem warunek jest w rzeczywistości spełniony. Rozwiązaniem jest użycie ===zamiast ==unikania niejawnej konwersji.

Ten przykład ilustruje, w jaki sposób kombinacja niejawnej konwersji i dynamicznego pisania może sprowadzić programistów na manowce.

Porównaj to z Ruby:

val = "abc" + 123

co jest błędem czasu wykonywania, ponieważ w Rubim obiekt 123 nie jest niejawnie konwertowany tylko dlatego, że zdarza się, że jest przekazywany do +metody. W Ruby programista musi wyraźnie zaznaczyć konwersję:

val = "abc" + 123.to_s

Porównanie PHP i Ruby jest tutaj dobrą ilustracją. Oba są językami dynamicznie wpisywanymi, ale PHP ma wiele niejawnych konwersji, a Ruby (być może zaskakujące, jeśli się z tym nie zaznajomisz) nie ma.

Statyczne / dynamiczne vs mocne / słabe

Chodzi o to, że oś statyczna / dynamiczna jest niezależna od osi silnej / słabej. Ludzie mylą je prawdopodobnie częściowo, ponieważ pisanie mocne i słabe jest nie tylko mniej jasno określone, nie ma prawdziwej zgodności co do tego, co rozumie się przez mocne i słabe. Z tego powodu mocne / słabe pisanie jest bardziej odcieniem szarości niż czerni lub bieli.

Aby odpowiedzieć na twoje pytanie: innym sposobem na to, co jest w większości poprawne, jest stwierdzenie, że pisanie statyczne to bezpieczeństwo typu kompilacji w czasie kompilacji, a silne pisanie to bezpieczeństwo typu wykonawczego.

Powodem tego jest to, że zmienne w języku o typie statycznym mają typ, który należy zadeklarować i można go sprawdzić podczas kompilacji. Język o silnym typie ma wartości, które mają typ w czasie wykonywania, i programiście trudno jest obalić system typów bez sprawdzania dynamicznego.

Ale ważne jest, aby zrozumieć, że język może być statyczny / silny, statyczny / słaby, dynamiczny / silny lub dynamiczny / słaby.


Zamiast powiedzieć, że $ s jest liczbą całkowitą lub łańcuchem, byłoby lepiej, gdybyś powiedział, że typ jest powiązany z „abcd” lub 1234 nie ze zmienną $ s.
Srinivas Reddy Thatiparthy

Doskonała odpowiedź z jasnymi przykładami. Myślę jednak, że nie rozwiązuje to całkowicie zamieszania, dlaczego DLACZEGO ludzie pytają o silną / statyczną parę bliźniaczych pojęć. Na przykład sformułowanie OP: „czy wpisywanie statyczne NIE WYMAGA pisania silnego?” Twoja odpowiedź podkreśla ich niezależność. Aby kontynuować wyjaśnienie, dlaczego silny często ŁĄCZY się ze statycznym, poprzednia odpowiedź Normana Ramseya jest bardzo dobra: stackoverflow.com/questions/376611/…
JasDev

1
"abc" + 123jest błędem środowiska wykonawczego , a nie błędem kompilacji w Rubim. Gdyby to był błąd kompilacji, ruby ​​zostałby wpisany statycznie.
sepp2k

Przykłady słabego pisania wymagają ulepszenia (patrz moja odpowiedź), ale poza tym fajne formatowanie.
Adam Gent

W moim przypadku silna vs słaba typgin jest następująca: Strong: „c” + True = błąd czasu wykonania lub błąd czasu kompilacji. Słaby: „c” + True = „b” lub „d”, ponieważ wszystko jest traktowane jako surowe bajty. Strong: C #, Ruby, C ++ Weak: Assembly, C (z powodu niejawnych wskaźników pustki)
Jonathan Allen

17

Oba są biegunami na dwóch różnych osiach:

  • mocno napisane vs. słabo napisane
  • typowanie statyczne vs. dynamiczne

Mocno wpisane oznacza, że ​​a nie będzie automatycznie konwertowane z jednego typu na inny. Słabo wpisane jest odwrotnie: Perl może użyć łańcucha jak "123"w kontekście numerycznym, automatycznie konwertując go na int 123. Silnie napisany język, taki jak python, tego nie zrobi.

Oznacza typ statyczny , kompilator oblicza typ każdej zmiennej w czasie kompilacji. Języki o typie dynamicznym wykrywają tylko typy zmiennych w czasie wykonywania.


6
Muszę się nie zgodzić. Język silnie typowany to taki, który wie, jakie typy są w czasie wykonywania. Słabo wpisany język to taki, który nie lubi montażu. Twój przykład jest na trzeciej osi, „konwersje niejawne vs. jawne”.
Jonathan Allen,

1
Właściwie to normalnie zgadzam się z Jonathanem, ale nie musisz mieć dostępnych typów w czasie wykonywania, aby być silnym typem, jeśli wykonasz pełną analizę statyczną i nie zezwolisz na rzutowanie. (patrz moja zredagowana odpowiedź).
Adam Gent

1
Python jest przykładem języka
pisanego

13

Silnie wpisane oznacza, że ​​istnieją ograniczenia między konwersjami między typami. Wpisanie statyczne oznacza, że ​​typy nie są dynamiczne - nie można zmienić typu zmiennej po jej utworzeniu.


Aby to zademonstrować: w silnie napisanym języku nie można porównywać „5” == 5 i przekonać się, że to prawda: ciągi nie są liczbami całkowitymi. Jeśli moja pamięć służy, większość współczesnych języków skryptowych jest silnie dynamicznie wpisywana. Tcl / Tk jest jednak słabo wpisany - wszystko można traktować jak ciąg.
Małe tabele Bobby

Bobby, w słabo wpisanym języku „5” == 5 jest odczytywany jako 0x35 == 0x05. Innymi słowy, wszystko jest traktowane jak surowe bajty.
Jonathan Allen,

Muszę się z tobą nie zgodzić. Weź Lua; możesz porównać „5” == 5, a to zwróci fałsz, jednak można szybko dokonać konwersji, przechodząc do „5” + 0.
RCIX

12

Koercja danych niekoniecznie oznacza słabo wpisany, ponieważ czasami jego cukier składniowy:

Powyższy przykład Java jest słabo wpisany z powodu

String s = "abc" + 123;

Nie jest słabo wpisanym przykładem, ponieważ naprawdę działa:

String s = "abc" + new Integer(123).toString()

Koercja danych również nie jest słabo typowana, jeśli budujesz nowy obiekt. Java jest bardzo złym przykładem słabo napisanego (a każdy język, który ma dobre odbicie, najprawdopodobniej nie będzie słabo napisany). Ponieważ środowisko wykonawcze języka zawsze wie, jaki jest typ (wyjątkiem mogą być typy rodzime).

W przeciwieństwie do C. C jest jednym z najlepszych przykładów słabo typowanych. Środowisko wykonawcze nie ma pojęcia, czy 4 bajty to liczba całkowita, struktura, wskaźnik lub 4 znaki.

Środowisko wykonawcze języka naprawdę określa, czy jego słabo napisane litery są w przeciwnym razie jego naprawdę sprawiedliwą opinią.

EDYCJA: Po dalszych przemyśleniach niekoniecznie jest to prawdą, ponieważ środowisko wykonawcze nie musi mieć poprawionych wszystkich typów w systemie wykonawczym, aby być silnie typowanym systemem. Haskell i ML mają tak kompletną analizę statyczną, że mogą potencjalnie pominąć informacje o typie ze środowiska wykonawczego.


B jest prawdopodobnie lepszym, choć nieco mniej znanym przykładem.
Tom Hawtin - tackline

JavaScript jest również dość słabym typem, ale ponieważ jest tak mało typów i ponieważ tak naprawdę nie można konstruować nowych typów.
Adam Gent

10

Odpowiedź jest już podana powyżej. Próba odróżnienia koncepcji silnej od tygodniowej od statycznej od dynamicznej.

Co to jest silnie napisane VS słabo wpisane?

Silnie wpisany: nie będzie automatycznie konwertowany z jednego typu na inny

W Go lub Python, podobnie jak w przypadku silnie wpisanych języków, „2” + 8 spowoduje błąd typu, ponieważ nie pozwalają one na „przymus typu”.

Słabo (luźno) wpisany: zostanie automatycznie przekonwertowany na jeden typ na inny: Słabo wpisane języki, takie jak JavaScript lub Perl, nie zgłoszą błędu, w tym przypadku JavaScript wyświetli „28”, a perl otrzyma 10.

Przykład Perla:

my $a = "2" + 8;
print $a,"\n";

Zapisz go na main.pl i uruchom perl main.pl a otrzymasz wynik 10.

Co to jest typ Statyczny VS Dynamiczny?

W programowaniu programista definiuje typowanie statyczne i dynamiczne w odniesieniu do punktu, w którym sprawdzane są typy zmiennych. Języki o typie statycznym to te, w których sprawdzanie typu odbywa się w czasie kompilacji, podczas gdy języki o typie dynamicznym to te, w których sprawdzanie typu odbywa się w czasie wykonywania.

  • Statyczne: typy sprawdzane przed uruchomieniem
  • Dynamiczny: typy sprawdzane w locie podczas wykonywania

Co to znaczy?

W Go sprawdza przed wpisaniem w czasie (kontrola statyczna). Oznacza to, że nie tylko tłumaczy i sprawdza kod, który wykonuje, ale skanuje cały kod, a błąd typu zostałby zgłoszony przed uruchomieniem kodu. Na przykład,

package main

import "fmt"

func foo(a int) {
    if (a > 0) {
        fmt.Println("I am feeling lucky (maybe).")
    } else {
        fmt.Println("2" + 8)
    }
}

func main() {
    foo(2)
}

Zapisz ten plik w main.go i uruchom go, otrzymasz komunikat o niepowodzeniu kompilacji.

go run main.go
# command-line-arguments
./main.go:9:25: cannot convert "2" (type untyped string) to type int
./main.go:9:25: invalid operation: "2" + 8 (mismatched types string and int)

Ale ten przypadek nie dotyczy Pythona. Na przykład następujący blok kodu zostanie wykonany dla pierwszego wywołania foo (2) i nie powiedzie się dla drugiego wywołania foo (0). Jest tak, ponieważ Python jest dynamicznie wpisywany, tłumaczy i sprawdza kod, na którym jest wykonywany. Blok else nigdy nie wykonuje się dla foo (2), więc „2” + 8 nigdy nie jest nawet oglądany, a dla wywołania foo (0) spróbuje wykonać ten blok i zakończy się niepowodzeniem.

def foo(a):
    if a > 0:
        print 'I am feeling lucky.'
    else:
        print "2" + 8
foo(2)
foo(0)

Zobaczysz następujące dane wyjściowe

python main.py
I am feeling lucky.
Traceback (most recent call last):
  File "pyth.py", line 7, in <module>
    foo(0)
  File "pyth.py", line 5, in foo
    print "2" + 8
TypeError: cannot concatenate 'str' and 'int' objects

8

Jedno nie implikuje drugiego. Aby język był statyczny wpisany , oznacza to, że typy wszystkich zmiennych są znane lub wywnioskowane w czasie kompilacji.

Język o silnym typie nie pozwala na używanie jednego typu jako drugiego. C jest językiem słabo wpisanym i jest dobrym przykładem tego, na co nie pozwalają języki o silnym typie. W C możesz przekazać element danych niewłaściwego typu i nie będzie narzekać. W silnie pisanych językach nie możesz.


7

Silne pisanie prawdopodobnie oznacza, że ​​zmienne mają dobrze zdefiniowany typ i że istnieją ścisłe zasady dotyczące łączenia zmiennych różnych typów w wyrażeniach. Na przykład, jeśli A jest liczbą całkowitą, a B jest liczbą zmiennoprzecinkową, wówczas ścisłą zasadą dotyczącą A + B może być to, że A jest rzutowane na liczbę zmiennoprzecinkową, a wynik jest zwracany jako liczba zmiennoprzecinkowa. Jeśli A jest liczbą całkowitą, a B jest łańcuchem, wówczas ścisła reguła może być taka, że ​​A + B jest niepoprawne.

Wpisywanie statyczne prawdopodobnie oznacza, że ​​typy są przypisywane w czasie kompilacji (lub jej odpowiednik dla języków nieskompilowanych) i nie mogą się zmieniać podczas wykonywania programu.

Zauważ, że te klasyfikacje nie wykluczają się wzajemnie, w rzeczywistości spodziewałbym się, że będą występować razem często. Wiele języków z silnym pisaniem jest również pisanych statycznie.

I zauważ, że kiedy używam słowa „prawdopodobnie”, dzieje się tak, ponieważ nie ma powszechnie akceptowanych definicji tych terminów. Jak już widzieliście z dotychczasowych odpowiedzi.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.