czwartek, 15 stycznia 2009

Dlaczego statyczne typowanie jest bez sensu

Dzisiaj będzie trochę dziwny post. Temat niby techniczny, ale nie mam zamiaru pokazywać tu kodu źródłowego, benchmarków itp.

Swoją przygodę z programowaniem zacząłem od wysokopoziomowych, dynamicznie typowanych języków (php, python, (trochę) ruby itd.). Czy to dobrze czy źle, trudno mi powiedzieć. Na dzień dzisiejszy wiem jednak, że nie wierzę w przyszłość kompilowanych, statycznie typowanych języków (jak c czy c++). Dlaczego? Ponieważ uważam, że niedługo system operacyjny oraz programy przez nas uruchamiane zmienią się diametralnie. Nie będziemy instalować programów na dysku, ale uruchamiać je w przeglądarce. System operacyjny będzie tylko łączył się z internetem oraz udostępniał przeglądarkę. Tam, czyli w aplikacjach webowych, używanie języków takich jak c++ jest kompletnie bez sensu (chociaż widziałem framework webowy dla c++ o.O). Temat ten rozwinę w innym poście, dzisiaj miało być o statycznym typowaniu :)

Teraz potrzebne będzie trochę wyobraźni (mówiłem, że dzisiejszy post będzie nietypowy, prawda?).
Wyobraźmy sobie szafę, w której mamy szuflady. W niektórych mamy bieliznę, w innych spodnie, w jeszcze innych koszule itd. Jeżeli chcemy wiedzieć co się w danej szufladzie znajduje, po prostu zaglądamy do niej. Z czasem nie musimy już zaglądać do każdej szuflady, pamiętamy już co znajduje się w każdej z nich.

Podobnie jest ze zmiennymi. Jeżeli chcemy wiedzieć co się w niej znajduje po prostu do niej zaglądamy ( zmienna = "costam" - o! ta zmienna zawiera string!). Dzięki odpowiednim nazwom, pamiętamy też co się w danej zmiennej znajduje ( lista_artykulow = ['jedzenie', 'picie', 'proszek do prania'])! Nie potrzebna jest więc nikomu informacja o typie zmiennej!

Tak wiem, że typujemy zmienne nie dla siebie, ale dla komputera. Dlaczego jednak twórcy niektórych języków programowania bardziej martwią się o dobro komputera niż o dobro programisty?!

21 komentarzy:

  1. Pytanie zasadnicze: czy zawsze (!) zaglądasz do każdej zmiennej, kiedy chcesz jej użyć? A zmienne właśnie typujemy dla siebie, żeby ograniczyć ilość głupich błędów popełnianych przez człowieka. Takie samo zadanie mają wszelkie modyfikatory „const”, „final”, itp. A że komputer (kompilator) przy okazji potrafi to wykorzystać do optymalizacji, to inna sprawa.

    OdpowiedzUsuń
  2. Statyczne typowanie jak najbardziej dba o dobro programisty. Dlatego kompilator jest w stanie pokazać błędy programisty, ew. wskazać podejrzane miejsca w kodzie. Nie mówiąc już o tym co może zrobić dla programisty dobre IDE w którym piszemy kod. Języki statycznie typowane, były i będą, są po prostu do innych zastosowań (jak każde narzędzie).

    OdpowiedzUsuń
  3. Statyczne typowanie i ścisła kontrola typów ma swoje zalety. Jedną z nich jest jednoznaczność. Weźmy na przykład taki kod javascriptowy:
    var z="1";
    z=z+1;
    co będzie zawierać zmienna z? 2? Czy może "11"?
    A to, że większość języków używana w technologiach webowych nie ma typowania wynika według mnie stąd, że przytłaczająca większość danych przetwarzanych w takich aplikacjach to albo stringi, albo ich kolekcje.

    OdpowiedzUsuń
  4. MCV: Najczęściej pamiętam, że zmienna title to string, a articles to słownik ;)

    Łukasz: Przecież interpreter też pokazuje błędy (te związane z typami też!). Oczywiście mówię tu o Pythonie/Rubym bo np. PHP zmienia sobie typy jak chce :/ IDE dla dynamicznie typowanych języków też potrafią wiele zdziałać ;)

    Czesiu: Ścisła kontrola typów - oczywiście! Bo inaczej wychodzą takie kwiatki - http://zestyping.livejournal.com/124503.html.

    OdpowiedzUsuń
  5. Ja akurat zgadzam się z Krzysztofem. Język sam powinien wykryć "typ zmiennej" zatem:
    $a=1
    $a+$a=2 a nie 11.
    Im bardziej komputery będą "rozumować jak ludzie" tym lepiej.

    OdpowiedzUsuń
  6. Dosyć ograniczone spojrzenie. Zarówno typowanie statyczne jak i dynamiczne ma swój sens. Nie ma jednego słusznego typowania. Typowanie statyczne ma swój narzut (np. zwiększa ilość kodu do napisania), ale im większy projekt tym bardziej to się zwraca. Typowanie dynamiczne bardzo dobrze sprawuje się w średnich i mniejszych aplikacjach (co nie znaczy, że nie da się pisać wielkich aplikacji). Typowanie statyczne pozwala na łatwiejszą refaktoryzację (wręcz niekiedy automatyczną), lepsze wspomaganie GUI. Można tak wymieniać dosyć długo, ale wniosek powinien być taki jak już napisałem: nie ma jednego słusznego podejścia. Osobiście uważam, że przyszłość to łączenie tych dwóch różnych podejść. Przykładem jest łączenie javy z jrubym, jythonem, albo c# z IronRuby, IronPython. Część aplikacji piszemy w języku statycznym, a część w dynamicznym.

    OdpowiedzUsuń
  7. "Lepsze wspomaganie GUI" - chodziło mi oczywiście o IDE (podpowiadanie kodu, dopisywanie kawałków kodu, znajdywanie błędów (już na poziomie pisania a nie kompilacji).

    OdpowiedzUsuń
  8. Krzysztof: ja też pamiętam, do czasu kiedy piszę również z innymi programistami. Swoje każdy pamięta, innych jakby mniej.

    A gdy interpreter pokazuje błąd, to może być już za późno. :-)

    „Im bardziej komputery będą "rozumować jak ludzie" tym lepiej.”

    Ha. Tym gorzej, bo różni ludzie rozumują inaczej. Komputery mają się zachowywać przewidywalnie. :þ

    OdpowiedzUsuń
  9. CZESIU Twój przykład nie ma nic wspólnego z typowaniem statycznym. Tutaj poruszyłeś inną kwestię, mianowicie typowania słabego/mocnego (http://en.wikipedia.org/wiki/Strongly-typed_programming_language i http://en.wikipedia.org/wiki/Weak_typing).

    OdpowiedzUsuń
  10. MCV: "A gdy interpreter pokazuje błąd, to może być już za późno." Nie za bardzo to rozumiem:

    Piszę program -> kompiluje -> wyrzuca mi błąd -> poprawiam -> kompiluję bez błędów -> uruchamiam

    Piszę program -> uruchamiam -> interpreter wyrzuca błąd -> poprawiam -> uruchamiam

    OdpowiedzUsuń
  11. Piszesz program -> nie dotestujesz czegoś (bo masz dużo na głowie) -> uruchamiasz na produkcji -> klienci widzą jak im się sypie. Bo zrobiłeś literówkę, albo coś tego rodzaju.

    OdpowiedzUsuń
  12. Dlaczego jednak twórcy niektórych języków programowania bardziej martwią się o dobro komputera niż o dobro programisty?!

    Gdyż co wygodniejsze dla ludzi, to jest wolniejsze. Sory, ale taka prawda. I ostatnio zauważyłem tendencję do skrajności, np. .NET, czy Java... Gdy widzę, że aplikacja jest napisana w pierwszym, czy drugim, to wolę poszukać odpowiednika napisanego z wykorzystaniem natywnych mechanizmów. Poza tym, zauważam tendencję dokładnie odwrotną do Twojego stwierdzenia.

    Dlaczego? .net: ktoś napisze pierdołę w najnowszym frameworku i muszę ściągać kilkadziesiąt MiB danych, zmarnować kilkaset tylko po to, aby zadziałał program o wadze kilkunastu KiB.
    Java: owszem, wygodny dla programistów, ale z wydajnością nie ma nic wspólnego. Wszyscy zachwalają sobie np. Eclipse i różne dodatki do niego, natomiast ja omijam szerokim łukiem ze względu na ospałość w działaniu...

    Możecie ględzić, że sieję herezje, ale przypomnijcie sobie czasy demosceny sprzed 15 lat... Ludzie się dwa razy zastanawiali, czy dana zmienna jest potrzebna, czy nie, co było dobre, a już nie jest.

    Ja akurat zgadzam się z Krzysztofem. Język sam powinien wykryć "typ zmiennej" zatem:
    A typowanie zmiennych jest dobre, gdyż oszczędza pamięć - np. liczba "123" zapisana w stringu zajmuje 3 bajty podczas, gdy z wykorzystaniem inta tylko jeden.

    PS. ~autor: okienko treści komentarza jest IMHO za małe.

    OdpowiedzUsuń
  13. I dzięki takiemu podejściu w PHP (0 == "cokolwiek") zwróci true

    OdpowiedzUsuń
  14. Wujciol: Dynamiczne typowanie != słabe typowanie!!! Spójrz co napisał Radarek kilka komentarzy wcześniej.

    OdpowiedzUsuń
  15. Weźmy prosty przykład - chcemy program, który wczytuje dwie dane, po czym je sumuje i zwraca wynik. Żeby nie było wątpliwości, że kwestia jest w statyczności/dynamiczności, a nie w słabości/silności typowania, załóżmy że nasz język jest najsilniej typowany jak tylko się da - 0 automatycznych konwersji typów.
    Zarys programu (żeby się nie zagłębiać w szczegóły implementacyjne) może wyglądać tak:

    a = wczytajDaną();
    b = wczytajDaną();
    c = a + b;
    return c;

    Proste, prawda? No niekoniecznie, bo oczywiście programiści popełniają błędy. Powiedzmy, że nasz kolega piszący funkcję wczytajDaną spartolił robotę i zapomniał o rzutowaniu/zamianie na inta gdzieś tam w kodzie, przez co jego funkcja nieoczekiwanie zwraca stringa.

    (I) Język z dynamiczną kontrolą typów
    Kompilacja (o ile w ogóle występuje) nic nie wykryje. W trakcie interpretacji/wykonania też nie dostaniemy żadnego komunikatu o błędzie, jeśli tylko język ma przeładowany "+" dla stringów. Efekt będzie taki, że zamiast zsumowanych liczb zwrócimy skonkatenowane stringi. W dodatku jeśli w końcu zauważymy, że program działa źle, zlokalizowanie, że problem leży w funkcji wczytajDaną z pewnością nie będzie oczywiste.

    (II) Język ze statyczną kontrolą typów
    Kompilator już na samym początku zauważy błąd - funkcja ma zwracać inta, a zwraca string. Gdyby jakimś cudem nie zauważył, to zwróci na to uwagę w co najmniej kilku innych miejscach - do zmiennych intowych a i b przypisujemy stringi, później do intowego c też, a na końcu sami zwracamy string zamiast inta. Każdy kawałek kodu świeciłby błędem. Co więcej zlokalizowanie przyczyny jest banalnie proste - kompilator najczęściej wskazuje konkretną linijkę, w której wystąpił błąd.

    Wiem, że przykład jest naiwny. Ale każdy popełnia błędy, a w swojej praktyce programistycznej nieraz spotykałem się z tak idiotycznymi pomyłkami - zarówno swoimi, jak i cudzymi - że w tej materii jestem w stanie uwierzyć praktycznie we wszystko.

    Patrząc bardziej szowinistycznie na sprawę, to zalety dynamicznego typowania są naprawdę mizerne - wygodę pisania kodu w pełni niweluje wygoda debuggowania kodu typowanego statycznie. W pewnych - z mojego punktu widzenia rzadkich - przypadkach pozwala na nieco większe możliwości. Za to po stronie typowania statycznego stoi szybkość, przewidywalność bezpieczeństwo i większa kontrola nad kodem. Języki typowane dynamicznie nie są nowością - Perl istnieje od ponad 20 lat, przed nim też były inne. Ale z jakiegoś powodu języki dynamicznie typowane nie zawojowały w dalszym ciągu świata większych programów desktopowych. Osobiście nie znam żadnego systemu operacyjnego, pakietu biurowego czy przeglądarki internetowej pisanej w dynamicznie typowanym języku. Oczywiście, języki dynamiczne nieźle się sprawdzają w pewnych zastosowaniach - do pisania skryptów czy aplikacji webowych, ale to nie jest i nigdy nie będzie cały świat informatyki. Czy wobec tego dalej uważasz, że "statyczne typowanie jest bez sensu"?

    Notabene, stwierdzenie "Tam, czyli w aplikacjach webowych, używanie języków takich jak c++ jest kompletnie bez sensu" jest bardzo mocno ryzykowne, polecam zapoznanie się z JSP.

    OdpowiedzUsuń
  16. typowanie statyczne jest bardzo dobre bo znajduje czasem ciezkie do wytropienia błędy...

    po za tym takie języki jak C++ (kompilowane natywnie) dzięki typowaniu wiedzą ile bajtów pamięci przerzucić na stos i robią to od razu, dzięki czemu są bardzo szybkie. C++ podczas wykonania nie zastanawia sie co siedzi w zmiennej - ten typ ma np 4 bajty (int) - rzuca instrukcja assemblera push 4 razy te cztery bajty i zmienna juz jest.

    A taki php najpierw musi sobie zapisac zmienna w swojej tablicy, sprawdzic jej typ, wykonac odpowiednie konwersje.. a czas leci.. ciezko powiedziec ile instrukcji assemblera to będzie ale myślę że rząd wielkości więcej niż w C++.

    OdpowiedzUsuń
  17. Przyznam szczerze, że przez te prawie 3 lata trochę zmieniło się moje podejście ;) Ale dalej uważam, że takie statyczne typowanie jak w C/C++ czy Javie jest zupełnie bez sensu. Dlaczego? Ano dlatego, że można stworzyć język który będzie przyjemny dla programisty a jednocześnie pod spodem będzie się zachowywał tak (i tak samo szybko działał) jak język statycznie typowany. Świetnym przykładem jest tutaj Scala, która jest co prawda statycznie typowana, ale posiada inferencję typów. To znaczy, tam gdzie kompilator może odgadnąć typ zmiennej (czy tego co zwróci funkcja) tam nie musimy tego explicite zaznaczać. Oczywiście czasem jest taka potrzeba - przy bardziej skomplikowanych funkcjach. Bardzo przydaje się jednak (i teraz czasami mi tego np. w Rubym brakuje) określania typu parametrów funkcji - tego Scala wymaga (tzn. nie możemy tego **nigdy** pominąć).

    Da się więc stworzyć język który ma i zalety języka statycznie typowanego (wsparcie IDE, lepsze działanie kompilatora, mniejsza podatność na błędy) i języka dynamicznie typowanego (nie musimy pisać bez przerwy rzeczy **oczywistych** - https://gist.github.com/1474405)

    Szczerze? Co mnie przy dzisiejszych komputerach obchodzą 4 bajty ;)

    OdpowiedzUsuń
  18. Odnośnie ostatniego komentarza: C++11 też ma inferencję typów: magiczne słowo "auto", które właśnie zmieniło znaczenie. Teraz już można np. robić tak:

    for (auto x: { -3_dB, 0_dB, 3_dB, 6_dB })
    ...

    Podobnie z lambdami, gdzie nie trzeba explicite podawać zwracanego typu. A jeszcze inny ciekawy przykład to język Pike, który jest dynamicznie typowany, ale można nakładać ograniczenia do typów zmiennych. I kompilator będzie sprawdzał typy przy kompilacji tam, gdzie zostały określone, a jeśli zajdzie potrzeba, to można sobie pofolgować i napisać coś w mniej restrykcyjny sposób. :-)

    OdpowiedzUsuń
  19. Czy bez silnego typowania możliwe byłoby używanie tablic asocjacyjnych (bez których nie wyobrażam sobie pisania w PHP)? Moim zdaniem to jest główna zaleta typów dynamicznych.

    OdpowiedzUsuń