Poszukiwanie wyjaśnień na temat pozornych sprzeczności dotyczących języków słabo pisanych

178

Myślę, że rozumiem mocne pisanie , ale za każdym razem, gdy szukam przykładów słabego pisania, znajduję przykłady języków programowania, które po prostu automatycznie wymuszają / konwertują typy.

Na przykład w tym artykule zatytułowanym Typing: Strong vs. Słaby, Static vs. Dynamic mówi, że Python jest silnie wpisywany, ponieważ otrzymasz wyjątek, jeśli spróbujesz:

Pyton

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

Jednak coś takiego jest możliwe w Javie i C # i nie uważamy ich za słabo wpisane tylko do tego.

Jawa

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

DO#

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

W tym innym artykule, zatytułowanym Weakly Type Languages, autor mówi, że Perl jest napisany słabo po prostu dlatego, że mogę połączyć ciąg z liczbą i odwrotnie bez żadnej wyraźnej konwersji.

Perl

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

Więc ten sam przykład sprawia, że ​​Perl jest słabo typizowany, ale nie Java i C # ?.

Ojej, to jest mylące wprowadź opis obrazu tutaj

Autorzy zdają się sugerować, że język, który uniemożliwia stosowanie pewnych operacji na wartościach różnych typów, jest silnie typizowany, a przeciwnie - słabo typizowany.

Dlatego w pewnym momencie poczułem się skłonny uwierzyć, że jeśli język zapewnia wiele automatycznych konwersji lub przymusu między typami (jak perl), może zostać uznany za słabo napisany, podczas gdy inne języki, które zapewniają tylko kilka konwersji, mogą skończyć się uważane za silnie wpisane.

Jestem jednak skłonny wierzyć, że w tej interpretacji muszę się mylić, po prostu nie wiem dlaczego ani jak to wyjaśnić.

Tak więc moje pytania to:

  • Co to naprawdę oznacza, że ​​język jest naprawdę słabo napisany?
  • Czy mógłbyś wymienić jakieś dobre przykłady słabego pisania, które nie są związane z automatyczną konwersją / automatycznym przymusem wykonywanym przez język?
  • Czy język może być słabo i silnie wpisany w tym samym czasie?
Edwin Dalorzo
źródło
8
Silne i słabe typowanie polega na konwersji typów (o co jeszcze może chodzić?). Jeśli szukasz przykładu „bardzo” słabego języka, obejrzyj to: Destroyallsoftware.com/talks/wat .
Wilduck
2
@Wildduck Wszystkie języki zapewniają konwersję typów, ale nie wszystkie z nich są uważane za słabo wpisane. Moje przykłady pokazane poniżej pokazują, w jaki sposób programiści uważają język słabo typizowany na podstawie tych samych przykładów, które są możliwe w innych językach uważanych za silnie typizowane. W związku z tym moje pytanie wciąż przeważa. Jaka jest różnica?
Edwin Dalorzo
1
Myślę, że krótka odpowiedź jest taka, że ​​„typowość” nie jest stanem binarnym. Java i C # są wpisywane silniej, ale nie absolutnie.
Jodrell
3
Uważam, że to lepiej pasuje do inżynierii oprogramowania .
zzzzBov
4
@Brendan A co z sumowaniem liczby zmiennoprzecinkowej i liczby całkowitej? Czy liczba całkowita nie jest przekształcana w liczbę zmiennoprzecinkową w Pythonie? Czy powiedziałbyś teraz, że Python nie jest absolutnie silnie wpisany?
Edwin Dalorzo

Odpowiedzi:

210

AKTUALIZACJA: To pytanie było tematem mojego bloga 15 października 2012. Dzięki za świetne pytanie!


Co tak naprawdę oznacza, że ​​język jest „słabo napisany”?

To znaczy „ten język używa systemu typów, który uważam za niesmaczny”. Natomiast język „silnie wpisany” to język z systemem typów, który wydaje mi się przyjemny.

Terminy są zasadniczo bez znaczenia i należy ich unikać. Wikipedia wymienia jedenaście różnych znaczeń wyrażenia „silnie wpisany”, z których kilka jest sprzecznych. Oznacza to, że prawdopodobieństwo pomyłki jest wysokie w każdej rozmowie, w której występuje termin „mocno wpisany” lub „napisany słabo”.

Wszystko, co możesz naprawdę powiedzieć z całą pewnością, to to, że omawiany język „silnie typowany” ma pewne dodatkowe ograniczenia w systemie typów, zarówno w czasie wykonywania, jak i kompilacji, których brakuje w omawianym języku „słabo typizowanym”. Bez dalszego kontekstu nie można określić, czym to ograniczenie może być.

Zamiast używać „silnie wpisanych” i „słabo wpisanych”, powinieneś szczegółowo opisać, jaki rodzaj bezpieczeństwa typów masz na myśli. Na przykład, C # jest statycznie wpisane język i bezpieczny rodzaj języka i bezpieczna pamięć język, w przeważającej części. C # pozwala na naruszenie wszystkich trzech form „silnego” pisania. Operator rzutowania narusza statyczne typowanie; mówi do kompilatora "Wiem więcej o typie środowiska wykonawczego tego wyrażenia niż ty". Jeśli deweloper się pomyli, środowisko uruchomieniowe zgłosi wyjątek w celu ochrony bezpieczeństwa typu. Jeśli twórca chce złamać bezpieczeństwo typu lub bezpieczeństwo pamięci, może to zrobić, wyłączając system bezpieczeństwa typu, tworząc „niebezpieczny” blok. W niebezpiecznym bloku możesz użyć magii wskaźnika, aby traktować int jako zmiennoprzecinkowy (narusza bezpieczeństwo typów) lub zapisywać do pamięci, której nie jesteś właścicielem. (Naruszenie bezpieczeństwa pamięci.)

C # nakłada ograniczenia typu, które są sprawdzane zarówno w czasie kompilacji, jak i w czasie wykonywania, dzięki czemu jest to język „silnie typizowany” w porównaniu z językami, które wykonują mniej kontroli w czasie kompilacji lub mniej w czasie wykonywania. C # pozwala również w szczególnych okolicznościach obejść te ograniczenia, czyniąc go językiem „słabo typizowanym” w porównaniu z językami, które nie pozwalają na takie zakończenie.

Co to naprawdę jest? Nie można powiedzieć; zależy to od punktu widzenia mówiącego i jego stosunku do różnych cech języka.

Eric Lippert
źródło
14
@edalorzo: Opiera się na gustach i osobistych opiniach na temat (1), które aspekty teorii typów są istotne, a które nie, oraz (2) czy język jest wymagany do egzekwowania lub jedynie zachęcania do ograniczeń typu. Jak wskazałem, można rozsądnie powiedzieć, że C # jest silnie wpisywany, ponieważ umożliwia i zachęca do statycznego pisania, i równie rozsądnie można powiedzieć, że jest napisany słabo, ponieważ umożliwia naruszenie bezpieczeństwa typów.
Eric Lippert
4
@edalorzo: Jeśli chodzi o montaż, to znowu kwestia opinii. Kompilator języka asemblera nie pozwoli ci przenieść 64-bitowego double ze stosu do rejestru 32-bitowego; Pozwoli to na przeniesienie 32-bitowego wskaźnika do 64-bitowego double ze stosu do rejestru 32-bitowego. W tym sensie język jest „bezpieczny dla typów” - nakłada ograniczenie legalności programu na podstawie klasyfikacji typów danych. To, czy to ograniczenie jest „silne”, czy „słabe”, jest kwestią opinii, ale jest to oczywiście ograniczenie.
Eric Lippert
2
Myślę, że teraz rozumiem twój punkt widzenia, naprawdę słabo zapisany język musiałby być całkowicie pozbawiony typu lub monotypowy, co w prawdziwym życiu jest praktycznie niemożliwe. W związku z tym każdy język ma określoną definicję typów, które są bezpieczne iw zależności od liczby dziur, które język zapewnia, aby naruszać lub manipulować jego danymi lub typami danych, możesz uznać, że jest on mniej lub bardziej słabo wpisany, być może nawet w tylko w niektórych kontekstach.
Edwin Dalorzo
7
@edalorzo: Dobrze. Na przykład nietypowy rachunek lambda jest tak słabo wpisany, jak to tylko możliwe. Każda funkcja jest funkcją od funkcji do funkcji; dowolne dane mogą być przekazywane do dowolnej funkcji bez ograniczeń, ponieważ wszystko jest „tego samego typu”. Ważność wyrażenia w rachunku lambda bez typu zależy tylko od jego formy składniowej, a nie od analizy semantycznej, która klasyfikuje określone wyrażenia jako mające określone typy.
Eric Lippert
3
@Mark, dałbym mu jeszcze jedno +1 za przewidzenie, że każdy poda inne interpretacje na ten temat. To „słabe pisanie” wydaje się być „mityczną koncepcją” lub „miejską legendą”, wszyscy to widzieli, ale nikt nie może udowodnić, że istnieje :-)
Edwin Dalorzo
64

Jak zauważyli inni, terminy „mocno wpisane” i „słabo wpisane” mają tak wiele różnych znaczeń, że nie ma jednej odpowiedzi na Twoje pytanie. Jednakże, ponieważ w swoim pytaniu wyraźnie wspomniałeś o Perlu, spróbuję wyjaśnić, w jakim sensie Perl jest napisany słabo.

Chodzi o to, że w Perlu nie ma czegoś takiego jak „zmienna całkowita”, „zmienna typu float”, „zmienna łańcuchowa” czy „zmienna boolowska”. W rzeczywistości, o ile użytkownik może (zazwyczaj) powiedzieć, że nie ma nawet całkowitą, pływak, ciąg lub wartość logiczna wartości : wszystko co musisz to „skalary”, które są wszystkie te rzeczy jednocześnie. Możesz więc na przykład napisać:

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

Oczywiście, jak słusznie zauważyłeś, wszystko to można postrzegać jako wymuszenie typu. Ale chodzi o to, że w Perlu typy są zawsze wymuszane. W rzeczywistości użytkownikowi trudno jest określić, jaki może być wewnętrzny „typ” zmiennej: w linii 2 w powyższym przykładzie pytanie, czy wartość $barjest ciągiem, "9"czy liczbą, 9jest prawie bez znaczenia, ponieważ jeśli chodzi o Perla, to jest to samo . Rzeczywiście, jest nawet możliwe, aby skalar Perla miał wewnętrznie zarówno ciąg znaków, jak i wartość liczbową w tym samym czasie, jak ma to miejsce na przykład w przypadku $foopo linii 2 powyżej.

Odwrotną stroną tego wszystkiego jest to, że ponieważ zmienne Perla nie mają określonego typu (lub raczej nie ujawniają swojego typu wewnętrznego użytkownikowi), operatory nie mogą być przeciążane, aby robić różne rzeczy dla różnych typów argumentów; nie możesz po prostu powiedzieć „ten operator zrobi X dla liczb i Y dla łańcuchów”, ponieważ operator nie może (nie) powiedzieć, jakiego rodzaju wartości są jego argumenty.

Na przykład Perl ma i potrzebuje zarówno liczbowego operatora dodawania ( +), jak i operatora konkatenacji ciągów ( .): jak widzieliśmy powyżej, dodawanie łańcuchów ( "1" + "2" == "3") lub łączenie liczb ( 1 . 2 == 12) jest całkowicie w porządku . Podobnie liczbowe operatory porównania ==, !=, <, >, <=, >=i <=>porównać wartości numerycznych swoich argumentów, a operatory porównania ciąg eq, ne, lt, gt, le, gei cmpporównać je leksykograficznie jako ciągi. Tak 2 < 10, ale 2 gt 10(ale "02" lt 10, podczas gdy "02" == 2). (Pamiętaj, że niektóre inne języki, takie jak JavaScript, starają się dostosować do pisania w Perlu słaberównież przeciąża operatora. Często prowadzi to do brzydoty, takiej jak utrata skojarzeń +).

(Mucha w maści polega na tym, że ze względów historycznych Perl 5 ma kilka przypadków narożnych, takich jak bitowe operatory logiczne, których zachowanie zależy od wewnętrznej reprezentacji ich argumentów. Są one ogólnie uważane za irytującą wadę projektową, ponieważ reprezentacja wewnętrzna może się zmienić z zaskakujących powodów, więc przewidywanie, co te operatory robią w danej sytuacji, może być trudne).

Wszystko powiedziane, że można argumentować, że Perl ma mieć silne typy; po prostu nie są typami, których można by się spodziewać. W szczególności, oprócz omówionego powyżej typu „skalarnego”, Perl ma również dwa typy strukturalne: „tablicę” i „hasz”. Są one bardzo różne od skalarów, do tego stopnia, że ​​zmienne Perla mają różne sigile wskazujące na ich typ ( $dla skalarów, @dla tablic, %dla skrótów) 1 . Tam zasady przymusu między tymi typami, więc można napisać na przykład %foo = @bar, ale wiele z nich jest bardzo stratny: na przykład, $foo = @barprzypisuje długość tablicy @bardo$foo, a nie jego zawartość. (Istnieje również kilka innych dziwnych typów, takich jak typeglobs i uchwyty I / O, których często nie widać.)

Niewielką luką w tym ładnym projekcie jest również istnienie typów referencyjnych, które są szczególnym rodzajem skalarów (i które można odróżnić od zwykłych skalarów za pomocą refoperatora). Możliwe jest użycie odniesień jako normalnych skalarów, ale ich wartości łańcuchowe / numeryczne nie są szczególnie przydatne i mają tendencję do tracenia specjalnego odniesienia, jeśli zmodyfikujesz je za pomocą normalnych operacji skalarnych. Ponadto każdą zmienną Perla 2 można przypisać blessdo klasy, zamieniając ją w obiekt tej klasy; system klas OO w Perlu jest nieco ortogonalny w stosunku do opisanego powyżej systemu typów pierwotnych (lub beztypu), chociaż jest również „słaby” w sensie pisania kaczegoparadygmat. Ogólna opinia jest taka, że ​​jeśli stwierdzisz, że sprawdzasz klasę obiektu w Perlu, robisz coś złego.


1 Właściwie pieczęć oznacza typ wartości są dostępne, tak że na przykład pierwsza skalarne w tablicy @foojest oznaczona $foo[0]. Więcej szczegółów znajdziesz w perlfaq4 .

2 Obiekty w Perlu są (normalnie) dostępne poprzez odniesienia do nich, ale to, co faktycznie jest blessedytowane, to (prawdopodobnie anonimowa) zmienna, na którą wskazuje odniesienie. Jednak błogosławieństwo jest rzeczywiście właściwością zmiennej, a nie jej wartością, więc np. Przypisanie rzeczywistej błogosławionej zmiennej do innej daje tylko jej płytką, nie pobłogosławioną kopię. Więcej informacji znajdziesz w perlobj .

Ilmari Karonen
źródło
19

Oprócz tego, co powiedział Eric, rozważ następujący kod C:

void f(void* x);

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

W przeciwieństwie do języków takich jak Python, C #, Java lub inne, powyższe jest słabo wpisane, ponieważ tracimy informacje o typie. Eric poprawnie wskazał, że w C # możemy obejść kompilator poprzez rzutowanie, skutecznie mówiąc mu „Wiem więcej o typie tej zmiennej niż ty”.

Ale nawet wtedy środowisko wykonawcze nadal będzie sprawdzać typ! Jeśli rzutowanie jest nieprawidłowe, system wykonawczy przechwyci go i zgłosi wyjątek.

W przypadku wymazywania typów tak się nie dzieje - informacje o typach są wyrzucane. Rzut do void*w C robi dokładnie to. Pod tym względem powyższe różni się zasadniczo od deklaracji metody C #, takiej jak void f(Object x).

(Z technicznego punktu widzenia C # umożliwia również usuwanie typów za pomocą niebezpiecznego kodu lub kierowania).

To jest tak słabo wpisane, jak to tylko możliwe. Cała reszta to tylko kwestia statycznego i dynamicznego sprawdzania typu, tj. Czasu, w którym typ jest sprawdzany.

Konrad Rudolph
źródło
1
+1 Słuszna uwaga, teraz pomyślałem o usuwaniu typu jako funkcji, która może również oznaczać „słabe pisanie na maszynie”. W Javie istnieje również wymazywanie typów, a w czasie wykonywania system typów pozwoli ci naruszyć ograniczenia, których kompilator nigdy by nie zatwierdził. Przykład w C doskonale ilustruje ten punkt.
Edwin Dalorzo
1
Zgoda, są warstwy cebuli lub piekła. Wydaje się, że jest to bardziej znacząca definicja słabości typu.
Jodrell
1
@edalorzo Nie sądzę, żeby to było dokładnie to samo, ponieważ chociaż Java pozwala na obejście kompilatora, system typów środowiska uruchomieniowego nadal wykryje naruszenie. W związku z tym system typów środowiska uruchomieniowego Java jest silnie wpisany w tym względzie (istnieją wyjątki, np. Gdzie odbicie może być użyte do obejścia kontroli dostępu).
Konrad Rudolph
1
@edalorzo W ten sposób możesz tylko obejść kompilator, a nie system wykonawczy. Ważne jest, aby zdać sobie sprawę, że języki takie jak Java i C # (a do pewnego stopnia także C ++) mają system typów, który jest zapewniany dwukrotnie: raz w czasie kompilacji i raz w czasie wykonywania. void*przerywa sprawdzanie obu typów. Wymazywanie typu ogólnego tego nie robi, omija tylko sprawdzanie czasu kompilacji. Pod tym względem jest dokładnie tak, jak wyraźne odlewy (wspomniane przez Erica).
Konrad Rudolph
1
@edalorzo Re twój błąd: nie powinniśmy. Rozróżnienie jest płynne. I tak, wymazywanie typów sprawia, że ​​Java jest słabo wpisywana pod tym względem. Chodziło mi o to, że nawet przy usuwaniu typów ogólnych nadal nie można obejść kontroli typów w czasie wykonywania, chyba że używasz również odbicia .
Konrad Rudolph
14

Doskonały przykład pochodzi z artykułu Strong Typing na Wikipedii :

Generalnie silne typowanie oznacza, że ​​język programowania nakłada poważne ograniczenia na mieszanie, które jest dozwolone.

Słabe pisanie

a = 2
b = "2"

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

Silne pisanie

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

Zwróć uwagę, że słaby język pisania na klawiaturze może łączyć różne typy bez błędów. Silny język typów wymaga, aby typy wejściowe były oczekiwanymi typami. W silnym języku typów typ można konwertować ( str(a)konwertuje liczbę całkowitą na łańcuch) lub cast ( int(b)).

Wszystko zależy od interpretacji pisania.

SaulBack
źródło
3
Ale to prowadzi do sprzecznych przykładów podanych w pytaniu. Język z silną typizacją może zawierać niejawny wymuszenie, co oznacza, że ​​jeden z dwóch przykładów „błędów typu” (lub oba) są automatycznie konwertowane na odpowiedni z dwóch pozostałych przykładów, ale generalnie ten język jest nadal silnie wpisywany.
Mark Hurd,
3
Prawdziwe. Myślę, że można powiedzieć, że istnieją różne stopnie silnego i słabego pisania. Niejawna konwersja może oznaczać, że język jest mniej silnie wpisany na typ niż język, który nie dokonuje niejawnej konwersji.
SaulBack
4

Chciałbym wnieść swój wkład do dyskusji własnymi badaniami na ten temat, ponieważ inni komentują i wnoszą swój wkład. Czytałem ich odpowiedzi i śledziłem ich odniesienia i znalazłem interesujące informacje. Jak zasugerowano, jest prawdopodobne, że większość z nich byłaby lepiej omówiona na forum programistów, ponieważ wydaje się być bardziej teoretyczna niż praktyczna.

Z teoretycznego punktu widzenia myślę, że artykuł Luca Cardellego i Petera Wegnera zatytułowany O zrozumieniu typów, abstrakcji danych i polimorfizmie zawiera jeden z najlepszych argumentów, jakie przeczytałem.

Typ może być postrzegany jako zestaw ubrań (lub zbroja), która chroni podstawową, nietypową reprezentację przed przypadkowym lub niezamierzonym użyciem. Zapewnia osłonę ochronną, która ukrywa podstawową reprezentację i ogranicza sposób, w jaki obiekty mogą oddziaływać z innymi obiektami. W systemie bez typu obiekty bez typu są nagie , ponieważ podstawowa reprezentacja jest widoczna dla wszystkich. Naruszenie systemu oznaczeń polega na zdjęciu ochronnego zestawu odzieży i operowaniu bezpośrednio na nagim przedstawieniu.

To stwierdzenie wydaje się sugerować, że słabe pisanie pozwoliłoby nam uzyskać dostęp do wewnętrznej struktury typu i manipulować nim tak, jakby był czymś innym (innym typem). Być może co moglibyśmy zrobić z niebezpiecznym kodem (wspomnianym przez Erica) lub ze wskaźnikami wymazanymi przez typ c, o których wspomniał Konrad.

Artykuł jest kontynuowany ...

Języki, w których wszystkie wyrażenia są zgodne z typem, nazywane są językami silnie wpisanymi. Jeśli język jest silnie wpisany, jego kompilator może zagwarantować, że zaakceptowane przez niego programy będą działać bez błędów typu. Ogólnie rzecz biorąc, powinniśmy dążyć do silnego pisania i przyjmować typowanie statyczne, gdy tylko jest to możliwe. Zauważ, że każdy język z typowaniem statycznym jest silnie typowany, ale odwrotność niekoniecznie jest prawdą.

Jako takie, mocne pisanie oznacza brak błędów typowych, mogę tylko założyć, że słabe pisanie oznacza coś przeciwnego: prawdopodobną obecność błędów. W czasie wykonywania czy kompilacji? Wydaje się tu nieistotne.

Zabawne, zgodnie z tą definicją język z potężnymi wymuszeniami typów, jak Perl, byłby uważany za silnie wpisany na typ, ponieważ system nie zawodzi, ale radzi sobie z typami, zmuszając je do odpowiednich i dobrze zdefiniowanych odpowiedników.

Z drugiej strony, można powiedzieć niż naddatkiem ClassCastExceptioni ArrayStoreException(w Javie) i InvalidCastException, ArrayTypeMismatchException(w języku C #) wskazywałoby na poziom słabo pisania, przynajmniej w czasie kompilacji? Odpowiedź Erica wydaje się zgadzać z tym.

W drugim artykule zatytułowanym Programowanie typowe, zawartym w jednym z odnośników podanych w jednej z odpowiedzi na to pytanie, Luca Cardelli zagłębia się w koncepcję naruszeń typów:

Większość języków programowania systemu dopuszcza naruszenia dowolnego typu, niektóre bezkrytycznie, inne tylko w zastrzeżonych częściach programu. Operacje, które obejmują naruszenie typu, nazywane są niesprawnymi. Naruszenia typów dzielą się na kilka klas [wśród których możemy wymienić]:

Konwersje wartości podstawowych : obejmują konwersje między liczbami całkowitymi, wartościami logicznymi, znakami, zestawami itp. Nie ma tu potrzeby naruszania typu, ponieważ można zapewnić wbudowane interfejsy do wykonywania wymuszeń w sposób typowy dla dźwięku.

W związku z tym wymuszenia typu, takie jak te dostarczane przez operatorów, mogą być uważane za naruszenia typów, ale jeśli nie naruszają spójności systemu typów, możemy powiedzieć, że nie prowadzą do słabo typowanego systemu.

Na tej podstawie ani Python, Perl, Java ani C # nie są słabo wpisane.

Cardelli wspomina o dwóch typach wulgaryzmów, które bardzo dobrze rozważam w przypadkach naprawdę słabego pisania:

Arytmetyka adresów. W razie potrzeby powinien być wbudowany (niesprawny) interfejs, zapewniający odpowiednie operacje na adresach i konwersjach typów. Różne sytuacje obejmują wskaźniki do sterty (bardzo niebezpieczne przy przenoszeniu kolektorów), wskaźniki do stosu, wskaźniki do obszarów statycznych i wskaźniki do innych przestrzeni adresowych. Czasami indeksowanie tablic może zastąpić arytmetykę adresów. Mapowanie pamięci. Obejmuje to spojrzenie na obszar pamięci jako na tablicę nieustrukturyzowaną, mimo że zawiera ona ustrukturyzowane dane. Jest to typowe dla alokatorów i kolektorów pamięci.

Tego rodzaju rzeczy możliwe w językach takich jak C (wspomniane przez Konrada) lub przez niebezpieczny kod w .Net (wspomniane przez Erica) naprawdę oznaczałyby słabe pisanie.

Uważam, że jak dotąd najlepszą odpowiedzią jest Eric, ponieważ definicja tych pojęć jest bardzo teoretyczna, a jeśli chodzi o konkretny język, interpretacja wszystkich tych koncepcji może prowadzić do różnych dyskusyjnych wniosków.

Edwin Dalorzo
źródło
4

Słabe typowanie rzeczywiście oznacza, że ​​wysoki procent typów może być niejawnie wymuszony, próbując odgadnąć, co zamierzał koder.

Silne pisanie oznacza, że ​​typy nie są wymuszane lub przynajmniej wymuszane mniej.

Typowanie statyczne oznacza, że ​​typy zmiennych są określane w czasie kompilacji.

Wiele osób myliło ostatnio „wyraźnie wpisane” z „wyraźnie wpisane na maszynie”. „Manifestly typed” oznacza, że ​​jawnie deklarujesz typy zmiennych.

Python jest przeważnie silnie typizowany, chociaż możesz użyć prawie wszystkiego w kontekście logicznym, a logiczne mogą być używane w kontekście liczb całkowitych, a możesz użyć liczby całkowitej w kontekście zmiennoprzecinkowym. Nie jest jawnie wpisany, ponieważ nie musisz deklarować swoich typów (z wyjątkiem Cythona, który nie jest całkowicie Python, aczkolwiek interesujący). Nie jest również wpisywany statycznie.

C i C ++ są wyraźnie wpisywane, typowane statycznie i nieco silnie typowane, ponieważ deklarujesz swoje typy, typy są określane w czasie kompilacji i możesz mieszać liczby całkowite i wskaźniki lub liczby całkowite i podwójne, a nawet rzutować wskaźnik do jednego typu na wskaźnik do innego typu.

Haskell jest interesującym przykładem, ponieważ nie jest wyraźnie wpisany, ale jest również wpisany statycznie i silnie.

user1277476
źródło
+1 Ponieważ podoba mi się wymyślony termin „jawnie wpisany”, który kategoryzuje języki takie jak Java i C #, w których trzeba jawnie deklarować typy i odróżniać je od innych języków typu statycznego, takich jak Haskell i Scala, gdzie wnioskowanie o typie odgrywa ważną rolę i zazwyczaj jak mówisz, dezorientuje ludzi i sprawia, że ​​uważają, że te języki są wpisywane dynamicznie.
Edwin Dalorzo
3

Silne <=> słabe typowanie dotyczy nie tylko kontinuum tego, ile lub jak mało wartości jest automatycznie wymuszanych przez język dla jednego typu danych na inny, ale także tego, jak mocno lub słabo wpisywane są rzeczywiste wartości . W Pythonie i Javie, a głównie w C #, wartości mają swoje typy ustawione w kamieniu. W Perlu nie tak bardzo - tak naprawdę jest tylko kilka różnych typów wartości, które można przechowywać w zmiennej.

Otwórzmy po kolei skrzynki.


Pyton

Na przykład Python 1 + "1", +operator wywołuje __add__na typ intnadając mu ciąg "1"jako argument - Powoduje to jednak w NotImplemented:

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

Następnie interpreter próbuje __radd__z str:

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

Ponieważ się nie powiedzie, +operator zawodzi z wynikiem TypeError: unsupported operand type(s) for +: 'int' and 'str'. Jako taki, wyjątek nie mówi wiele o silnym typowaniu, ale fakt, że operator + nie wymusza automatycznie swoich argumentów na ten sam typ, wskazuje na fakt, że Python nie jest najsłabiej typizowanym językiem w kontinuum.

Z drugiej strony, w Pythonie 'a' * 5 jest realizowany:

>>> 'a' * 5
'aaaaa'

To jest,

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

Fakt, że operacja jest inna, wymaga pewnego silnego typowania - jednak przeciwieństwo *przekształcania wartości na liczby przed pomnożeniem nadal niekoniecznie powodowałoby, że wartości byłyby słabo typowane.


Jawa

Przykład w Javie String result = "1" + 1;działa tylko dlatego, że dla wygody operator +jest przeciążony ciągami. +Operator Java zastępuje sekwencję utworzeniem StringBuilder(zobacz to ):

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

Jest to raczej przykład bardzo statycznego pisania bez rzeczywistego przymusu - StringBuilderma metodę, append(Object)która jest tutaj specjalnie używana. Dokumentacja mówi, co następuje:

Dołącza ciąg reprezentujący Objectargument.

Ogólny efekt jest dokładnie taki, jakby argument został przekonwertowany na łańcuch za pomocą metody String.valueOf(Object), a znaki tego ciągu zostały następnie dołączone do tej sekwencji znaków.

Gdzie String.valueOfwtedy

Zwraca ciąg znaków reprezentujący argument Object. [Zwraca] jeśli argumentem jest null, to ciąg równy "null"; w przeciwnym razie zwracana obj.toString()jest wartość .

Jest to zatem przypadek absolutnego braku przymusu ze strony języka - delegowanie wszelkich trosk na same przedmioty.


DO#

Według odpowiedzi Jona Skeeta , operator +nie jest nawet przeciążony dla stringklasy - podobnie jak w Javie, jest to po prostu wygoda generowana przez kompilator, dzięki zarówno statycznemu, jak i silnemu typowaniu.


Perl

Jak wyjaśnia perldata ,

Perl ma trzy wbudowane typy danych: skalary, tablice skalarów i asocjacyjne tablice skalarów, znane jako „hashe”. Skalar to pojedynczy ciąg (dowolnego rozmiaru, ograniczony tylko dostępną pamięcią), liczba lub odniesienie do czegoś (co zostanie omówione w perlref). Tablice normalne są uporządkowanymi listami skalarów indeksowanych według numeru, zaczynając od 0. Hashe to nieuporządkowane zbiory wartości skalarnych indeksowane przez skojarzony z nimi klucz łańcuchowy.

Perl nie ma jednak oddzielnego typu danych dla liczb, wartości logicznych, łańcuchów, wartości null, undefineds, odniesień do innych obiektów itp. - ma tylko jeden typ dla wszystkich tych obiektów, typ skalarny; 0 jest wartością skalarną równą „0”. Zmienna skalarna , która została ustawiona jako ciąg znaków, może naprawdę zmienić się w liczbę i od tego momentu zachowywać się inaczej niż „zwykły łańcuch”, jeśli jest dostępna w kontekście numerycznym. Skalar może pomieścić wszystko w Perlu, jest to tyle samo obiektu, ile istnieje w systemie. podczas gdy w Pythonie nazwy odnoszą się tylko do obiektów, w Perlu wartości skalarne w nazwach są obiektami zmiennymi. Co więcej, do tego przyklejony jest system Object Oriented Type: w perlu są tylko 3 typy danych - skalary, listy i hashe. Obiekt zdefiniowany przez użytkownika w Perlu jest odniesieniem (czyli wskaźnikiem do dowolnego z 3 poprzednich) blesspakietów - możesz wziąć dowolną taką wartość i pobłogosławić ją dowolnej klasie w dowolnym momencie.

Perl pozwala nawet na zmianę klas wartości według kaprysu - nie jest to możliwe w Pythonie, gdzie aby utworzyć wartość jakiejś klasy, musisz jawnie skonstruować wartość należącą do tej klasy object.__new__lub podobną. W Pythonie nie da się tak naprawdę zmienić istoty obiektu po utworzeniu, w Perlu możesz zrobić wiele:

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";

w ten sposób tożsamość typu jest słabo związana ze zmienną i można ją zmienić za pomocą dowolnego odwołania w locie. W rzeczywistości, jeśli to zrobisz

my $another = $val;

\$anothernie ma tożsamości klasowej, mimo że \$valnadal będzie stanowić błogosławione odniesienie.


TL; DR

W Perlu jest o wiele więcej niż tylko automatyczne wymuszanie, ale chodzi o to, że same typy wartości nie są utrwalone, w przeciwieństwie do Pythona, który jest dynamicznym, ale bardzo silnie typizowanym językiem. Że pyton daje TypeErrorna 1 + "1"to wskazuje, że język jest silnie wpisany, choć wbrew jeden robi coś pożytecznego, jak w Java lub C # nie wyklucza je jako zdecydowanie wpisywanych języków.

Antti Haapala
źródło
To jest całkowicie zdezorientowane. To, że zmienne Perl 5 nie mają typów, nie ma żadnego związku z wartościami , które zawsze mają typ.
Jim Balter
@JimBalter Cóż, tak, wartość ma taki typ, że jest łańcuchem lub liczbą i może zachowywać się inaczej w pewnym kontekście, w zależności od tego, czy zmienna skalarna zawierała ciąg, czy liczbę; ale wartość zawarta w zmiennej może zmienić typ, po prostu uzyskując dostęp do zmiennej, a ponieważ sama wartość żyje w zmiennej, same wartości można uznać za zmienne między typami.
Antti Haapala
Wartości nie zmieniają typów - to niespójne; wartość jest zawsze typu . Wartość, którą zawiera zmienna, może ulec zmianie. Zmiana z 1 na „1” to taka sama zmiana wartości, jak zmiana z 1 na 2.
Jim Balter.
Język słabo typizowany, taki jak Perl, umożliwia niejawną zmianę poprzedniego typu wartości w zależności od kontekstu. Ale nawet C ++ umożliwia takie niejawne konwersje poprzez definicje operatorów. Słabe pisanie to bardzo nieformalna właściwość i naprawdę nie jest użytecznym sposobem opisywania języków, jak zauważył Eric Lippert.
Jim Balter
PS Można pokazać, że nawet w Perlu <digits> i "<digits>" mają różne wartości, a nie tylko różne typy. Perl sprawia, że ​​<digits> i "<digits>" wydają się mieć tę samą wartość w większości przypadków poprzez niejawne konwersje , ale iluzja nie jest kompletna; np. „12” | „34” to 36, a 12 | 34 to 46. Innym przykładem jest to, że „00” jest liczbowo równe 00 w większości kontekstów, ale nie w kontekście logicznym, gdzie „00” jest prawdą, ale 00 jest fałszem.
Jim Balter
1

Jak wielu innych wyraziło, całe pojęcie „silnego” i „słabego” pisania jest problematyczne.

Jako archetyp Smalltalk jest bardzo silnie wpisany - zawsze zgłosi wyjątek, jeśli operacja między dwoma obiektami jest niekompatybilna. Jednak podejrzewam, że niewielu na tej liście nazwałoby Smalltalk językiem z silną typizacją, ponieważ jest on wpisywany dynamicznie.

Uważam, że pojęcie „statyczne” i „dynamiczne” typowanie jest bardziej przydatne niż „mocne” i „słabe”. Język z typowaniem statycznym ma wszystkie typy wyliczone w czasie kompilacji, a programista musi jawnie zadeklarować, jeśli jest inaczej.

Porównaj z językiem dynamicznie wpisywanym, w którym pisanie jest wykonywane w czasie wykonywania. Jest to zazwyczaj wymagane w przypadku języków polimorficznych, więc programista nie musi z góry decydować o tym, czy operacja między dwoma obiektami jest legalna.

W językach polimorficznych, z typami dynamicznymi (jak Smalltalk i Ruby), bardziej przydatne jest myślenie o „typie” jako o „zgodności z protokołem”. Jeśli obiekt jest zgodny z protokołem w taki sam sposób, jak inny obiekt - nawet jeśli te dwa obiekty nie mają wspólnego dziedziczenia, mixinów ani innego voodoo - są uznawane przez system wykonawczy za ten sam „typ”. Mówiąc dokładniej, obiekt w takich systemach jest autonomiczny i może zdecydować, czy ma sens odpowiadać na jakąkolwiek konkretną wiadomość odnoszącą się do konkretnego argumentu.

Chcesz obiektu, który może dać sensowną odpowiedź na komunikat „+” z argumentem obiektu opisującym kolor niebieski? Możesz to zrobić w językach z dynamicznym typowaniem, ale jest to uciążliwe w przypadku języków z typowaniem statycznym.

Jan Steinman
źródło
3
Myślę, że koncepcja dynamicznego vs statycznego pisania nie jest przedmiotem dyskusji. Chociaż muszę powiedzieć, że nie wierzę, że polimorfizm i tak jest upośledzony w językach typu statycznego. Ostatecznie system typów sprawdza, czy dana operacja ma zastosowanie do danych operandów, czy to w czasie wykonywania, czy w czasie kompilacji. Ponadto inne formy polimorfizmu, takie jak funkcje parametryczne i klasy, umożliwiają łączenie typów w językach typu statycznego w sposób, który określiłeś jako bardzo trudny w porównaniu z typowaniem dynamicznym, nawet ładniejszy, jeśli zapewniono wnioskowanie o typie.
Edwin Dalorzo
0

Podoba mi się odpowiedź @Erica Lipperta , ale aby odpowiedzieć na to pytanie - języki silnie typowane zazwyczaj mają wyraźną wiedzę o typach zmiennych w każdym punkcie programu. Języki słabo typowane tego nie robią, więc mogą próbować wykonać operację, która może nie być możliwa dla określonego typu. Wydaje się, że najłatwiej to zobaczyć w funkcji. C ++:

void func(string a) {...}

aWiadomo, że zmienna jest typu string i każda niezgodna operacja zostanie przechwycona w czasie kompilacji.

Pyton:

def func(a)
  ...

Zmienna amoże być dowolna i możemy mieć kod, który wywołuje nieprawidłową metodę, która zostanie przechwycona tylko w czasie wykonywania.

Lubo Antonow
źródło
12
Myślę, że możesz mylić dynamiczne i statyczne pisanie z silnym i słabym pisaniem. W obu wersjach twojego kodu systemy typu runtime doskonale wiedzą, że a jest łańcuchem znaków. Tyle, że w pierwszym przypadku kompilator może ci to powiedzieć, w drugim nie. Ale to nie powoduje, że żaden z tych języków jest słabo typowany.
Edwin Dalorzo