Podczas ciekawości na głównej stronie witryny języka programowania skryptowego napotkałem ten fragment:
Kiedy system staje się zbyt duży, aby utrzymać go w głowie, możesz dodać typy statyczne.
Dzięki temu pamiętam, że w wielu wojnach religijnych między statycznymi, skompilowanymi językami (jak Java) a dynamicznymi, interpretowanymi językami (głównie Python, ponieważ jest częściej używany, ale jest to „problem” wspólny dla większości języków skryptowych), jeden z zarzutów jest statyczny Fani języków wpisywanych na języki dynamicznie wpisywane są tym, że nie skalują się dobrze do większych projektów, ponieważ „pewnego dnia zapomnisz o zwracanym typie funkcji i będziesz musiał to sprawdzić, podczas gdy w przypadku statycznie wpisywanych języków wszystko jest wyraźnie zadeklarowany ”.
Nigdy nie rozumiałem takich stwierdzeń. Szczerze mówiąc, nawet jeśli zadeklarujesz typ zwracany przez funkcję, możesz i zapomnisz ją po napisaniu wielu wierszy kodu, i nadal będziesz musiał wrócić do wiersza, w którym został zadeklarowany za pomocą funkcji wyszukiwania edytor tekstu, aby to sprawdzić.
Dodatkowo, gdy funkcje są deklarowane za pomocą type funcname()...
, nie wiedząc, type
że będziesz musiał przeszukać każdą linię, w której funkcja jest wywoływana, ponieważ wiesz tylko, że funcname
w Pythonie i tym podobnych możesz po prostu wyszukać def funcname
lub function funcname
co dzieje się tylko raz, o Deklaracja.
Co więcej, w REPLs testowanie funkcji pod kątem typu zwracanego przy użyciu różnych danych wejściowych jest banalne, podczas gdy w przypadku języków o typie statycznym trzeba by dodać kilka wierszy kodu i ponownie skompilować wszystko, aby poznać zadeklarowany typ.
Więc poza znajomością typu zwracanego przez funkcję, która wyraźnie nie jest mocną stroną języków o typie statycznym, w jaki sposób pisanie statyczne jest naprawdę pomocne w większych projektach?
źródło
Odpowiedzi:
To nie jest banalne. To nie jest trywialne w ogóle . Jest to trywialne, aby to zrobić tylko dla trywialnych funkcji.
Na przykład można w prosty sposób zdefiniować funkcję, w której typ zwracany zależy całkowicie od typu wejściowego.
W takim przypadku
getAnswer
tak naprawdę nie ma jednego typu zwrotu. Nie ma testu, który można kiedykolwiek napisać, który wywołałby to przy użyciu przykładowego wejścia, aby dowiedzieć się, jaki jest typ zwracany. Zawsze będzie zależeć od faktycznego argumentu. W czasie wykonywania.I to nawet nie obejmuje funkcji, które np. Wykonują wyszukiwanie w bazie danych. Lub rób rzeczy na podstawie danych wprowadzonych przez użytkownika. Lub poszukaj zmiennych globalnych, które są oczywiście typu dynamicznego. Lub zmień typ zwrotu w przypadkowych przypadkach. Nie wspominając już o konieczności ręcznego testowania każdej pojedynczej funkcji za każdym razem.
Zasadniczo udowodnienie typu zwrotu funkcji w ogólnym przypadku jest dosłownie niemożliwe matematycznie (problem zatrzymania). Tylko sposobem na zagwarantowanie typ zwracany jest ograniczenie wejście tak, że odpowiedzi na to pytanie nie wchodzi w domenie problemu stopu przez zabronienie programy, które nie są możliwe do udowodnienia, a to, co robi wpisując statyczne.
Języki o typie statycznym mają tak zwane „narzędzia”. Są to programy, które pomagają ci robić rzeczy z kodem źródłowym. W tym przypadku po prostu kliknę prawym przyciskiem myszy i przejdę do definicji, dzięki Resharper. Lub użyj skrótu klawiaturowego. Lub po prostu najedź myszką, a powie mi, jakie typy są zaangażowane. Nie dbam w najmniejszym stopniu o grepowanie plików. Sam edytor tekstowy jest żałosnym narzędziem do edycji kodu źródłowego programu.
Z pamięci
def funcname
nie wystarczyłoby w Pythonie, ponieważ funkcja może zostać dowolnie przypisana ponownie. Lub może być deklarowany wielokrotnie w wielu modułach. Lub na zajęciach. Itp.Wyszukiwanie plików w poszukiwaniu nazwy funkcji jest straszną, prymitywną operacją, która nigdy nie powinna być wymagana. Jest to fundamentalna awaria twojego środowiska i narzędzi. Fakt, że rozważyłbyś nawet potrzebę wyszukiwania tekstu w Pythonie, jest ogromnym argumentem przeciwko Pythonowi.
źródło
Pomyśl o projekcie z wieloma programistami, który zmieniał się na przestrzeni lat. Musisz to utrzymać. Jest funkcja
Co u licha to robi? Co jest
v
? Skądanswer
pochodzi ten element ?Teraz mamy więcej informacji -; potrzebuje pewnego rodzaju
AnswerBot
.Jeśli pójdziemy do języka klasowego, możemy powiedzieć
Teraz możemy mieć zmienną typu
AnswerBot
i wywołać metodę,getAnswer
a każdy wie, co robi. Wszelkie zmiany są przechwytywane przez kompilator przed wykonaniem testów środowiska wykonawczego. Istnieje wiele innych przykładów, ale może to daje pomysł?źródło
Wydaje się, że masz kilka nieporozumień na temat pracy z dużymi statycznymi projektami, które mogą zaciemniać Twoją ocenę. Oto kilka wskazówek:
Większość osób pracujących z językami o typie statycznym używa IDE dla tego języka lub inteligentnego edytora (takiego jak vim lub emacs), który ma integrację z narzędziami specyficznymi dla danego języka. Zazwyczaj istnieje szybki sposób na znalezienie rodzaju funkcji w tych narzędziach. Na przykład w przypadku środowiska Eclipse w projekcie Java istnieją dwa sposoby znalezienia typu metody:
someVariable.
), A Eclipse wyszukuje typsomeVariable
i wyświetla listę rozwijaną wszystkich metod zdefiniowanych w tym typie; kiedy przewijam listę, typ i dokumentacja każdego z nich jest wyświetlana, gdy jest on zaznaczony. Zauważ, że jest to bardzo trudne do osiągnięcia w przypadku dynamicznego języka, ponieważ edytorowi trudno jest (lub w niektórych przypadkach jest to niemożliwe) ustalić, jakiego typusomeVariable
jest, więc nie może łatwo wygenerować poprawnej listy. Jeśli chcę użyć metodythis
, mogę po prostu nacisnąć kombinację klawiszy Ctrl + spacja, aby uzyskać tę samą listę (chociaż w tym przypadku nie jest to tak trudne w przypadku języków dynamicznych).Jak widać, jest to nieco lepsze niż typowe narzędzie dostępne dla języków dynamicznych (nie jest to niemożliwe w językach dynamicznych, ponieważ niektóre mają całkiem dobrą funkcjonalność IDE - smalltalk jest tym, który przychodzi na myśl - ale trudniej jest dynamiczny język i dlatego jest mniej prawdopodobne, że będzie dostępny).
Narzędzia w języku statycznym zazwyczaj zapewniają funkcje wyszukiwania semantycznego, tzn. Potrafią precyzyjnie znaleźć definicję określonych symboli i odniesienia do nich, bez konieczności wyszukiwania tekstu. Na przykład, używając Eclipse do projektu Java, mogę podświetlić symbol w edytorze tekstu i kliknąć go prawym przyciskiem myszy i wybrać „przejdź do definicji” lub „znajdź odniesienia”, aby wykonać jedną z tych operacji. Nie musisz szukać tekstu definicji funkcji, ponieważ Twój edytor już wie dokładnie, gdzie ona jest.
Jednak odwrotność jest taka, że wyszukiwanie definicji metody na podstawie tekstu naprawdę nie działa tak dobrze w dużym dynamicznym projekcie, jak sugerujesz, ponieważ w takim projekcie może być łatwo wiele metod o tej samej nazwie i prawdopodobnie nie masz łatwo dostępne narzędzia do jednoznacznego wskazania, które z nich wywołujesz (ponieważ takie narzędzia są co najwyżej trudne do napisania lub w ogóle niemożliwe), więc musisz to zrobić ręcznie.
Nie jest niemożliwe posiadanie REPL dla języka o typie statycznym. Haskell to przykład, który przychodzi na myśl, ale istnieją również REPL dla innych języków o typie statycznym. Chodzi o to, że nie trzeba wykonywać kodu, aby znaleźć zwracany typ funkcji w języku statycznym - można to ustalić na podstawie badania bez potrzeby uruchamiania czegokolwiek.
Możliwe, że nawet gdybyś musiał to zrobić, nie musiałbyś ponownie wszystko kompilować . Większość współczesnych języków statycznych ma kompilatory przyrostowe, które skompilują tylko niewielką część zmienionego kodu, dzięki czemu można uzyskać niemal natychmiastową informację zwrotną o błędach typu, jeśli się je popełni. Na przykład Eclipse / Java podświetli błędy podczas pisania .
źródło
You seem to have a few misconceptions about working with large static projects that may be clouding your judgement.
Mam tylko 14 lat i programuję na Androida od niecałego roku, więc myślę, że to możliwe.Porównaj z powiedzmy javascript, Ruby lub Smalltalk, gdzie programiści redefiniują funkcjonalność języka podstawowego w czasie wykonywania. Utrudnia to zrozumienie dużego projektu.
Większe projekty to nie tylko więcej ludzi, mają więcej czasu. Wystarczająco dużo czasu, aby każdy mógł zapomnieć lub przejść dalej.
Anegdotycznie mój znajomy ma bezpieczne oprogramowanie „Job For Life” w Lisp. Nikt poza zespołem nie może zrozumieć podstawy kodu.
źródło
Anecdotally, an acquaintance of mine has a secure "Job For Life" programming in Lisp. Nobody except the team can understand the code-base.
czy to naprawdę takie złe? Czy dodana personalizacja nie pomaga im być bardziej produktywnym?Nie chodzi o to, że zapomnisz o typie zwrotu - zawsze tak się stanie. Chodzi o to, że narzędzie może poinformować, że zapomniałeś typu zwrotu.
Jest to kwestia składni, która jest całkowicie niezwiązana ze statycznym pisaniem.
Składnia rodziny C jest rzeczywiście nieprzyjazna, gdy chcesz wyszukać deklarację bez posiadania specjalistycznych narzędzi. Inne języki nie mają tego problemu. Zobacz składnię deklaracji Rust:
Każdy język może być interpretowany, a każdy język może mieć REPL.
Odpowiem w sposób abstrakcyjny.
Program składa się z różnych operacji, które są ułożone tak, jak są, z powodu pewnych założeń programisty.
Niektóre założenia są dorozumiane, a niektóre jawne. Niektóre założenia dotyczą operacji w pobliżu, inne dotyczą operacji poza nimi. Założenie jest łatwiejsze do zidentyfikowania, gdy jest wyrażone wprost i jak najbliżej miejsc, w których liczy się jego wartość prawdy.
Błąd jest manifestacją założenia, które istnieje w programie, ale w niektórych przypadkach nie obowiązuje. Aby wyśledzić błąd, musimy zidentyfikować błędne założenie. Aby usunąć błąd, musimy albo usunąć to założenie z programu, albo zmienić coś, aby założenie faktycznie się utrzymało.
Chciałbym podzielić założenia na dwa rodzaje.
Pierwszy rodzaj to założenia, które mogą, ale nie muszą, zależeć od danych wejściowych programu. Aby zidentyfikować błędne założenie tego rodzaju, musimy szukać w przestrzeni wszystkich możliwych danych wejściowych programu. Stosując wyrafinowane domysły i racjonalne myślenie, możemy zawęzić problem i szukać w znacznie mniejszej przestrzeni. Ale mimo to, gdy program rośnie jeszcze trochę, jego początkowa przestrzeń wejściowa rośnie w ogromnym tempie - do tego stopnia, że można go uznać za nieskończony dla wszystkich praktycznych celów.
Drugi rodzaj to założenia, które zdecydowanie obowiązują dla wszystkich danych wejściowych lub są zdecydowanie błędne dla wszystkich danych wejściowych. Kiedy stwierdzimy, że tego rodzaju założenie jest błędne, nie musimy nawet uruchamiać programu ani testować żadnych danych wejściowych. Kiedy stwierdzimy, że tego rodzaju założenie jest prawidłowe, mamy jednego podejrzanego o mniejszą wagę, gdy będziemy śledzić błąd ( dowolny błąd). Dlatego warto mieć tyle założeń, ile to możliwe, należy do tego rodzaju.
Aby umieścić założenie w drugiej kategorii (zawsze prawdziwe lub zawsze fałszywe, niezależne od danych wejściowych), potrzebujemy minimalnej ilości informacji, aby były dostępne w miejscu, w którym dokonano założenia. W całym kodzie źródłowym informacje dość szybko stają się nieaktualne (na przykład wiele kompilatorów nie przeprowadza analizy międzyproceduralnej, co sprawia, że każde wywołanie stanowi twardą granicę dla większości informacji). Potrzebujemy sposobu, aby zachować wymagane informacje (aktualne i dostępne w pobliżu).
Jednym ze sposobów jest umieszczenie źródła tych informacji jak najbliżej miejsca, w którym ma zostać wykorzystany, ale w większości przypadków może to być niepraktyczne. Innym sposobem jest częste powtarzanie informacji, odnawianie ich znaczenia w kodzie źródłowym.
Jak już można się domyślić, typy statyczne są dokładnie takie - sygnały nawigacyjne informacji o typie rozproszone w kodzie źródłowym. Informacje te można wykorzystać do umieszczenia większości założeń dotyczących poprawności typu w drugiej kategorii, co oznacza, że prawie każdą operację można zaklasyfikować jako zawsze poprawną lub zawsze niepoprawną pod względem zgodności typu.
Gdy nasze typy są niepoprawne, analiza oszczędza nam czas, zwracając uwagę na błąd wcześniej, niż późno. Gdy nasze typy są poprawne, analiza oszczędza nam czas, zapewniając, że gdy wystąpi błąd, możemy natychmiast wykluczyć błędy typu.
źródło
Pamiętasz stare powiedzenie „wyrzucanie śmieci, wyrzucanie śmieci”, cóż, właśnie temu pomaga statyczne pisanie. To nie jest uniwersalne panaceum, ale ścisłość tego, jakie dane rutyna przyjmuje i zwraca, oznacza, że masz pewność, że z nią poprawnie pracujesz.
Zatem procedura getAnswer, która zwraca liczbę całkowitą, nie będzie przydatna, gdy spróbujesz użyć jej w wywołaniu łańcuchowym. Pisanie statyczne już mówi ci, abyś uważał, że prawdopodobnie popełniasz błąd. (i oczywiście, możesz to zastąpić, ale musisz dokładnie wiedzieć, co robisz, i określić to w kodzie za pomocą rzutowania. Zasadniczo jednak nie chcesz tego robić - włamywanie się do okrągły kołek w kwadratowy otwór nigdy nie działa dobrze)
Teraz możesz pójść dalej, używając złożonych typów, tworząc klasę, która ma funkcjonalność rudy, możesz zacząć przekazywać je i nagle zyskujesz znacznie więcej struktury w swoim programie. Programy ustrukturyzowane to takie, które są znacznie łatwiejsze do poprawnego działania, a także do utrzymania.
źródło