Stopniowe pisanie: „Niemal każdy język ze statycznym systemem typów ma również system dynamicznych typów”

20

Twierdzenie to przez Aleks Bromfield stwierdza:

Prawie każdy język ze statycznym systemem typów ma również dynamiczny system typów. Oprócz C nie mogę wymyślić wyjątku

Czy jest to uzasadnione roszczenie? Rozumiem, że dzięki klasom refleksyjnym lub ładującym w środowisku uruchomieniowym Java jest trochę podobna - ale czy ten pomysł „stopniowego pisania” można rozszerzyć na wiele języków?

Sokole Oko
źródło
Nie jestem ekspertem od języków, ale kilka, które od razu przychodzą mi na myśl, że nie wierzę, że mają dynamiczne typy (natywnie, nie mówiąc, że nie można w nich coś połączyć) - Fortran, Ada, Pascal, Cobol
mattnz
4
Jestem ekspertem od języków i nie jestem pewien, o co twierdzi ten facet. „statyczny” i „dynamiczny” są mylnymi znakami i zwykle wskazują czas sprawdzania / analizy typu (w czasie kompilacji lub w czasie wykonywania). Niektóre języki łączą oba te elementy (np. Przez niedozwolone operacje na nieobsługiwanych typach podczas kompilacji i zgłaszanie wyjątków, takich jak ClassCastException podczas uruchamiania), i to może mieć na myśli. Jeśli tak, to prawdą jest, że C zwykle nie wykonuje sprawdzania typu w czasie wykonywania. Mówiąc lang. nie ma „dynamicznego systemu typów” nie ma sensu.
Thiago Silva,

Odpowiedzi:

37

Oryginalny tweeter tutaj. :)

Przede wszystkim jestem nieco rozbawiony / zszokowany tym, że mój tweet jest traktowany tak poważnie! Gdybym wiedział, że będzie to tak szeroko rozpowszechnione, spędziłbym ponad 30 sekund na pisaniu tego!

Thiago Silva słusznie zauważa, że ​​„statyczny” i „dynamiczny” dokładniej opisują sprawdzanie typu , a nie systemy typu . W rzeczywistości stwierdzenie, że język jest wpisywany statycznie lub dynamicznie, jest niedokładne. Język ma raczej system typów, a implementacja tego języka może wymusić system typów przy użyciu sprawdzania statycznego lub dynamicznego, albo jedno i drugie, lub oba (choć nie byłaby to bardzo atrakcyjna implementacja językowa!).

Tak się składa, że ​​istnieją pewne systemy typów (lub cechy systemów typów), które są bardziej podatne na sprawdzanie statyczne, i są pewne systemy typów (lub cechy systemów typów), które są bardziej podatne na sprawdzanie dynamiczne. Na przykład, jeśli twój język pozwala ci określić w tekście programu, że określona wartość musi zawsze być tablicą liczb całkowitych, to dość rozsądnie jest napisać statyczny moduł sprawdzający, aby zweryfikować tę właściwość. I odwrotnie, jeśli twój język ma podtypy i jeśli pozwala na downcasting, to dość łatwo jest sprawdzić poprawność downcastu w czasie wykonywania, ale niezwykle trudno to zrobić w czasie kompilacji.

To, co naprawdę rozumiem przez mój tweet, to po prostu to, że zdecydowana większość implementacji językowych wykonuje pewną część dynamicznego sprawdzania typów. Lub, równoważnie, ogromna większość języków ma pewne funkcje, które są trudne (jeśli nie niemożliwe) do sprawdzenia statycznego. Przykładem jest downcasting. Inne przykłady obejmują przepełnienie arytmetyczne, sprawdzanie granic tablic i sprawdzanie wartości NULL. Niektóre z nich można sprawdzić statycznie w niektórych okolicznościach, ale ogólnie rzecz biorąc, trudno byłoby znaleźć implementację języka, która nie wykonuje żadnej kontroli w czasie wykonywania.

to nie jest zła rzecz. To tylko spostrzeżenie, że istnieje wiele interesujących właściwości, które chcielibyśmy egzekwować w naszych językach i że tak naprawdę nie wiemy, jak to sprawdzić statycznie. Jest to przypomnienie, że rozróżnienia, takie jak „typy statyczne” w porównaniu z „typami dynamicznymi”, nie są tak wyraźne, jak niektórzy chcieliby, by ci się wydawało. :)

Ostatnia uwaga: terminy „silny” i „słaby” nie są tak naprawdę używane w społeczności badaczy języków programowania i nie mają tak naprawdę spójnego znaczenia. Ogólnie stwierdziłem, że kiedy ktoś mówi, że język ma „mocne pisanie”, a jakiś inny język ma „słabe pisanie”, to tak naprawdę mówi, że ich ulubiony język (ten z „mocnym pisaniem”) uniemożliwia mu popełniając jakiś błąd, że inny język (ten z „słabym pisaniem”) nie - lub odwrotnie, że ich ulubiony język (ten z „słabym pisaniem”) pozwala im zrobić coś fajnego, że drugi język ( jeden z „silnym pisaniem”) nie.

Aleks Bromfield
źródło
9
„To tylko spostrzeżenie, że istnieje wiele interesujących właściwości, które chcielibyśmy egzekwować w naszych językach i że tak naprawdę nie wiemy, jak to sprawdzić statycznie”. - Cóż, nie chodzi tylko o to, że nie wiemy, jak to zrobić. „Czy ten program halt dla tego wejścia” jest ciekawą właściwość, że nie tylko nie wiem jak sprawdzić, ale w rzeczywistości możemy zrobić, wiem, to niemożliwe do sprawdzenia.
Jörg W Mittag,
„Inne przykłady obejmują przepełnienie arytmetyczne, sprawdzanie granic tablic i sprawdzanie wartości zerowej.” Wystarczy pamiętać, że chociaż istnieje kilka języków, które sprawdzają te właściwości w systemie typów, nawet w najbardziej dynamicznie pisanych językach są one zazwyczaj cechą wartości , a nie typów. (Sprawdzanie „zerowe” jest jednak wspólną cechą systemów typu statycznego.)
porglezomp
Nie jest to typ dynamiczny układ , a statyczny typ systemu . Nawet jeśli żadne z nich nie jest egzekwowane, nadal tam są, opisując, co jest poprawne, a co błędne. Język / implementacja może, ale nie musi, sprawdzać jedno z nich. BTW, statyczny i dynamiczny system typów powinien być spójny, ale z definicji tak nie jest .
Elazar
Każdy język ma dynamiczny system typów, który jest zbiorem reguł zabraniających wykonywania pewnych operacji.
Elazar
7

No tak. Możesz mieć torby własności w dowolnym statycznie pisanym języku. Składnia będzie okropna, a jednocześnie zyskasz wszystkie wady dynamicznie typowanego systemu. Tak naprawdę nie ma żadnej przewagi, chyba że kompilator pozwala na użycie ładniejszej składni, co dynamicrobi coś w stylu C # .

Możesz również zrobić to dość łatwo w C.

W odpowiedzi na inne odpowiedzi: Myślę, że ludzie mylą pisanie statyczne / dynamiczne z pisaniem mocnym / słabym. Wpisywanie dynamiczne polega na możliwości zmiany struktury danych w czasie wykonywania, a na wykorzystaniu kodu, który odpowiada potrzebom kodu. Nazywa się to pisaniem kaczek .

Wspominanie o refleksji nie mówi całej historii, ponieważ refleksja nie pozwala na zmianę istniejącej struktury danych. Nie możesz dodać nowego pola do klasy lub struktury w języku C, C ++, Java lub C #. W językach dynamicznych dodawanie nowych pól lub atrybutów do istniejących klas jest nie tylko możliwe, ale w rzeczywistości dość powszechne.

Na przykład spójrz na Cython , kompilator Python-to-C. Tworzy statyczny kod C, ale system typów wciąż zachowuje swój dynamiczny charakter. C jest językiem o typie statycznym, ale jest w stanie obsługiwać dynamiczne pisanie z języka Python.

Euforyk
źródło
Technicznie rzecz biorąc, możesz to zrobić w języku C # od wersji 4 ExpandoObject, chociaż jest to proces opt-in, w przeciwieństwie do JavaScript lub Ruby. Mimo to podjąłeś bardzo ważną kwestię, którą jest pisanie kaczek (co 99% programistów tak naprawdę znaczy, gdy mówią „dynamicznie pisane”) i refleksja to wcale nie to samo.
Aaronaught
Mogę też dodać, że Python nie ma prawdziwego pisania kaczego. Ma tylko kilka haczyków do „zaimplementowania własnego” (ma magiczną metodę, która może powrócić True, aby powiedzieć „ten zwariowany obiekt jest instancją klasy, którą definiuję”). OCaml ma tę funkcję, o ile rozumiem, ale tak naprawdę nie wiem.
vlad-ardelean
6

Języki dynamiczne to języki statyczne . To, co jest powszechnie nazywane „pisaniem dynamicznym”, jest tak naprawdę szczególnym przypadkiem pisania statycznego - przypadek, w którym ograniczyłeś się do posiadania tylko jednego typu. Jako eksperyment myślowy wyobraź sobie pisanie programu w Javie lub C # przy użyciu tylko Objectzmiennych / pól / parametrów i rzucanie w dół bezpośrednio przed wywołaniem dowolnej metody. Bardziej dokładne byłoby nazywanie języków takich jak Python lub JavaScript „unityped”. (To twierdzenie prawdopodobnie dezorientuje / przeszkadza wielu osobom, biorąc pod uwagę, że taki program Java lub C # użyłby wielu podtypów, ale dzieje się tak, ponieważ przeciętny język OOP łączy typy i klasy. Przeczytaj więcej postów na blogu).

Zauważ, że nawet C ma „dynamiczne” pisanie - możesz rzucić dowolny wskaźnik na void(i jeśli pamięć mi to służy char) wskaźnik iz powrotem. Zauważ też, że nie ma tam sprawdzania czasu wykonywania; jeśli się pomylisz, ciesz się swoim niezdefiniowanym zachowaniem!

Doval
źródło
Ale obsada odbywa się statycznie. Nie rozumiem, jak to jest przykład pisania dynamicznego.
osa
@osa Tylko dlatego, że kod mówi, String foo = (String) barże to nie znaczy, że bartak naprawdę jest String. Możesz to wiedzieć tylko na pewno w czasie wykonywania, więc nie widzę, jak obsada jest wykonywana „statycznie”.
Doval
Nie zgadzam się z tym. Przynajmniej w Javascripcie możesz dodawać nowe metody i właściwości do obiektów w środowisku wykonawczym. W języku C # musisz jawnie użyć dynamicobiektu, aby to zrobić. Jeśli spróbujesz dodać właściwość do object... cóż, nie możesz.
Arturo Torres Sánchez,
3

Różnica między statycznych i dynamicznych typowania jest gdy typ wartości jest sprawdzana: kompilacji czas pracy w porównaniu. W językach, w których wartości niosą ze sobą swój typ (np. Obiekty Java), zawsze możesz skorzystać z pisania dynamicznego, nawet jeśli język rzeczywiście woli pisać statycznie. Oto przykład w Javie z dynamicznie wpisywaną metodą:

void horribleJava(List untypedList) {
  for (Object o : untypedList)
    ((SpecificType) o).frobnicate();
}

Zwróć uwagę, w jaki sposób sprawdzany jest typ każdego elementu w czasie wykonywania. Równoważna metoda o typie statycznym to:

void goodJava(List<SpecificType> typedList) {
  for (SpecificType o : typedList) {
    o.forbnicate();
  }
}

W C wartości (a konkretnie wskaźniki) nie zachowują swojego typu podczas działania - każdy wskaźnik jest równoważny a void *. Zamiast tego zmienne i wyrażenia mają typ. Aby osiągnąć dynamiczne pisanie, musisz sam nosić informacje o typie (np. Jako pole w strukturze).

amon
źródło
1
Nie sądzę, by obsada liczyła się jako dynamiczne pisanie w jakimkolwiek naprawdę praktycznym sensie. Nadal wymaga znajomości typu w czasie kompilacji, po prostu odkłada faktyczne sprawdzenie poprawności do czasu wykonania. Nie możesz frobnicatetutaj wywołać tej metody bez uprzedniej wiedzy o niej SpecificType.
Aaronaught
2
@Aaronaught Ale to jest dynamiczna wpisywanie raz mamy odroczone czek typu biec razem. Zauważ, że oba moje przykłady używają typowania nominalnego, które wymaga określonej nazwy typu. Zastanawiasz się nad typowaniem strukturalnym , np. Pisaniem kaczek, które wymaga obecności jakiejś metody. Osie strukturalne vs. nominalne i statyczne vs. dynamiczne są względem siebie ortogonalne (Java jest nominalna i głównie statyczna; ML i Go są strukturalne i statyczne; Perl, Python i Ruby są (głównie) strukturalne i dynamiczne).
amon
Jak powiedziałem w moim ostatnim komentarzu, jest to teoretyczne rozróżnienie, na którym nie ma żadnego programisty, którego kiedykolwiek spotkałem. Możesz z grubsza argumentować, że ludzie używają niewłaściwej terminologii (i faktycznie wygląda na to, że oryginalny głośnik wysokotonowy dążył właśnie do tego rodzaju nieokreśloności), ale dla ludzi, którzy faktycznie są w okopach, dynamiczne pisanie = pisanie kaczek. W rzeczywistości definicja jest tak powszechna, że ​​języki takie jak C # faktycznie ją skodyfikowały (tj. dynamicSłowem kluczowym). Zrównanie czasu statycznego z czasem kompilacji i dynamicznego ze środowiskiem wykonawczym przeważnie powoduje zamulenie wód.
Aaronaught
@Aaronaught: Jeśli ktoś rzuci na interfejs, a jeśli klasy, które implementują metodę o zamierzonym znaczeniu, konsekwentnie implementują ten sam interfejs, by tak powiedzieć, to zasadniczo pisanie kaczką w czasie wykonywania. Chociaż niektórzy mogą argumentować, że „wpisywanie kaczki” powinno po prostu używać nazwy metody, myślę, że bardziej pomocne byłoby uznanie „rzeczywistej” nazwy metody za nazwę interfejsu plus nazwę metody. W przeciwnym razie, należy [1,2].Add([3,4])wydajność [1,2,3,4], [1,2,[3,4]]albo [4,6]?
supercat
@ superupat: Żadne z powyższych nie powinno się powieść, ponieważ nie ma Addmetody na tablicy, która akceptuje tablicę, ponieważ taka metoda byłaby niejednoznaczna. Pisanie kaczką nie usprawiedliwia pisania zrozumiałych typów i funkcji.
Aaronaught
2

Pisanie statyczne vs. dynamiczne zasadniczo odnosi się do sposobu sprawdzania typów. Wpisywanie statyczne oznacza, że ​​weryfikacja typów różnych zmiennych lub wyrażeń jest sprawdzana na podstawie rzeczywistego kodu (zwykle przez kompilator), natomiast w systemie typów dynamicznych weryfikacja ta jest wykonywana tylko w czasie wykonywania przez środowisko wykonawcze.

Uważam, że tekst odnosi się do tego, że nawet jeśli typy są faktycznie sprawdzane statycznie, są one również sprawdzane w czasie wykonywania, tj. Dynamicznie. Poprawnie wspomniałeś Java Reflection; odbicie odbywa się tylko w czasie wykonywania, a środowisko Java Runtime Environment (JVM) faktycznie sprawdza typ, gdy używane jest odbicie, co w zasadzie oznacza dynamiczne sprawdzanie typu.

m3th0dman
źródło
To nie jest poprawne. W C ++, Pascal, Fortran --- we wszystkich skompilowanych językach --- typy są sprawdzane tylko statycznie, a nie dynamicznie (chyba że używasz dynamicznego_castu). Możesz użyć wskaźników do zapisania dowolnych rzeczy w pamięci i nikt nie będzie wiedział, jakie są typy. Tak więc pisanie statyczne oznacza WYŁĄCZNIE STATYSTYCZNE, podczas gdy pisanie dynamiczne oznacza TYLKO SPRAWDZANIE W CZASIE PRACY.
osa
-1

Wykluczenie jest błędne: C ma również ważny dynamiczny system typów. Po prostu tego nie sprawdza („C jest mocno wpisany, słabo zaznaczony”). Na przykład traktowanie struktury jako double( reinternpret_caststylu) daje niezdefiniowane zachowanie - błąd typu dynamicznego.

Elazar
źródło
(Do każdego, kto przegłosował - komentarz @Thiago Silva mówi o tej samej rzeczy)
Elazar,