W jaki sposób pisanie statyczne jest naprawdę pomocne w większych projektach?

9

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 funcnamew Pythonie i tym podobnych możesz po prostu wyszukać def funcnamelub function funcnameco 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?

użytkownik6245072
źródło
2
jeśli przeczytasz odpowiedzi na inne pytanie, prawdopodobnie uzyskasz odpowiedzi na to pytanie, w zasadzie pytają o to samo z różnych perspektyw :)
sara
1
Swift i place zabaw są REPL w języku o typie statycznym.
daven11
2
Języki nie są kompilowane, implementacje są. Sposobem na napisanie REPL dla „skompilowanego” języka jest napisanie czegoś, co może zinterpretować ten język lub przynajmniej skompilować i wykonać wiersz po wierszu, utrzymując niezbędny stan. Ponadto Java 9 zostanie dostarczona z REPL.
Sebastian Redl,
2
@ user6245072: Oto jak zrobić REPL dla tłumacza: przeczytaj kod, wyślij go do tłumacza, wydrukuj wynik. Oto jak zrobić REPL dla kompilatora: przeczytaj kod, wyślij go do kompilatora, uruchom skompilowany kod , wydrukuj wynik. Łatwe jak ciasto. To właśnie robią FSi (F♯ REPL), GHCi (GHC Haskell's REPL), Scala REPL i Cling.
Jörg W Mittag,

Odpowiedzi:

21

Co więcej, w REPLs testowanie funkcji pod kątem typu zwracanego przy różnych wejściach jest banalne

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.

getAnswer(v) {
 return v.answer
}

W takim przypadku getAnswertak 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.

getAnswer(x, y) {
   if (x + y.answer == 13)
       return 1;
   return "1";
}

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.

Dodatkowo, ponieważ funkcje są deklarowane za pomocą typu funcname () ..., bez znajomości typu, będziesz musiał przeszukać każdą linię, w której funkcja jest wywoływana, ponieważ znasz tylko funcname, podczas gdy w Pythonie i tak dalej wyszukaj def funcname lub funcname funkcji, która dzieje się tylko raz, w deklaracji.

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 funcnamenie 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.

i nadal będziesz musiał wrócić do wiersza, w którym został zadeklarowany za pomocą funkcji wyszukiwania edytora tekstu, aby to sprawdzić.

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.

DeadMG
źródło
2
Szczerze mówiąc, te „narzędzia” zostały wynalezione w językach dynamicznych, a języki dynamiczne miały je na długo przed powstaniem języków statycznych. Idź do definicji, uzupełnianie kodu, automatyczne refaktoryzowanie itp. Istniały w graficznych IDE Lisp i Smalltalk, zanim języki statyczne miały nawet grafikę lub IDE, nie mówiąc już o graficznych IDE.
Jörg W Mittag
Znając typ zwracany funkcji nie zawsze powiedzieć, jakie funkcje ZROBIĆ . Zamiast pisać typy, możesz napisać testy dokumentów z przykładowymi wartościami. na przykład porównaj (słowa „niektóre słowa oue”) => [„niektóre”, „słowa”, „oeu”] z (słowa ciąg) -> [ciąg], (zip {abc} [1..3]) => [(a, 1), (b, 2), (c, 3)] z rodzajem.
aoeu256,
18

Pomyśl o projekcie z wieloma programistami, który zmieniał się na przestrzeni lat. Musisz to utrzymać. Jest funkcja

getAnswer(v) {
 return v.answer
}

Co u licha to robi? Co jest v? Skąd answerpochodzi ten element ?

getAnswer(v : AnswerBot) {
  return v.answer
}

Teraz mamy więcej informacji -; potrzebuje pewnego rodzaju AnswerBot.

Jeśli pójdziemy do języka klasowego, możemy powiedzieć

class AnswerBot {
  var answer : String
  func getAnswer() -> String {
    return answer
  }
}

Teraz możemy mieć zmienną typu AnswerBoti wywołać metodę, getAnswera 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ł?

daven11
źródło
1
Wygląda już wyraźniej - chyba że zauważysz, że taka funkcja nie ma powodu, by istnieć, ale to oczywiście tylko przykład.
user6245072
To jest problem, gdy masz wielu programistów w dużym projekcie, takie funkcje istnieją (i gorzej), to koszmar. rozważ również funkcje w dynamicznych językach, które znajdują się w globalnej przestrzeni nazw, więc z czasem możesz mieć kilka funkcji getAnswer - i oba działają i oba są różne, ponieważ są ładowane w różnym czasie.
daven11
1
Myślę, że to powoduje nieporozumienie programowania funkcjonalnego. Co jednak masz na myśli mówiąc, że znajdują się w globalnej przestrzeni nazw?
user6245072
3
„funkcje w dynamicznych językach są domyślnie w globalnej przestrzeni nazw” jest to szczegół specyficzny dla języka, a nie ograniczenie spowodowane dynamicznym pisaniem.
sara,
2
@ daven11 „Myślę, że javascript tutaj”, rzeczywiście, ale inne dynamiczne języki mają rzeczywiste przestrzenie nazw / moduły / pakiety i mogą cię ostrzec przed redefinicjami. Być może trochę przesadzasz.
Coredump
10

Wydaje się, że masz kilka nieporozumień na temat pracy z dużymi statycznymi projektami, które mogą zaciemniać Twoją ocenę. Oto kilka wskazówek:

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 edytora tekstowego Sprawdź to.

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:

  • Jeśli chcę użyć metody na innym obiekcie niż „this”, wpisuję odwołanie i kropkę (np. someVariable.), A Eclipse wyszukuje typ someVariablei 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 typu someVariablejest, więc nie może łatwo wygenerować poprawnej listy. Jeśli chcę użyć metody this, 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).
  • Jeśli mam już odniesienie do określonej metody, mogę na nim przesunąć kursor myszy, a typ i dokumentacja metody są wyświetlane w etykiecie narzędzi.

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).

Dodatkowo, ponieważ funkcje są deklarowane za pomocą typu funcname () ..., bez znajomości typu, będziesz musiał przeszukać każdą linię, w której funkcja jest wywoływana, ponieważ znasz tylko funcname, podczas gdy w Pythonie i tak dalej wyszukaj def funcname lub funcname funkcji, która dzieje się tylko raz, w deklaracji.

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.

Co więcej, w REPLs testowanie funkcji pod kątem typu zwracanego przy różnych wejściach jest banalne

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.

natomiast w przypadku języków o statycznym typie trzeba dodać kilka wierszy kodu i ponownie skompilować wszystko, aby poznać zadeklarowany typ.

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 .

Jules
źródło
4
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.
user6245072,
1
Nawet bez IDE, jeśli usuniesz metodę z klasy w Javie i istnieją rzeczy, które zależą od tej metody, dowolny kompilator Java da ci listę każdej linii, która używała tej metody. W Pythonie kończy się niepowodzeniem, gdy wykonywany kod wywołuje brakującą metodę. Regularnie używam zarówno Javy, jak i Pythona. Uwielbiam Pythona za to, jak szybko możesz działać i fajne rzeczy, które możesz zrobić, których Java nie obsługuje, ale w rzeczywistości mam problemy z programami Python, które po prostu się nie zdarzają (prosta) Java. W szczególności refaktoryzacja jest znacznie trudniejsza w Pythonie.
JimmyJames
6
  1. Ponieważ sprawdzanie statyczne jest łatwiejsze dla języków o typie statycznym.
    • Co najmniej, bez dynamicznych funkcji językowych, jeśli się kompiluje, to w czasie wykonywania nie ma nierozwiązanych funkcji. Jest to powszechne w projektach ADA i C na mikrokontrolerach. (Programy mikrokontrolerów czasami stają się duże ... jak setki kloc dużych.)
  2. Statyczne sprawdzanie referencji kompilacji jest podzbiorem niezmienników funkcji, które w języku statycznym można również sprawdzić w czasie kompilacji.
  3. Języki statyczne mają zazwyczaj większą przejrzystość referencyjną. W rezultacie nowy programista może zagłębić się w pojedynczy plik i zrozumieć niektóre z nich, naprawić błąd lub dodać niewielką funkcję bez konieczności poznawania wszystkich dziwnych rzeczy w bazie kodu.

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.

Tim Williscroft
ź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?
user6245072 17.07.16
@ user6245072 Może to być zaleta dla osób obecnie tam pracujących, ale utrudnia rekrutację nowych osób. Więcej czasu zajmuje znalezienie kogoś, kto już zna język inny niż główny nurt, lub nauczenie go takiego, którego jeszcze nie zna. Może to utrudnić zwiększenie skali projektu, gdy odniesie on sukces, lub odzyskanie równowagi po fluktuacji - ludzie odchodzą, awansują na inne stanowiska ... Po pewnym czasie może to być również niekorzystne dla samych specjalistów - po napisaniu jakiegoś języka nich przez dekadę może być trudno przejść do czegoś nowego.
Hulk
Czy nie możesz po prostu użyć znacznika do stworzenia testów jednostkowych z działającego programu Lisp? Podobnie jak w Pythonie możesz utworzyć dekorator (przysłówek) o nazwie print_args, który przyjmuje funkcję i zwraca zmodyfikowaną funkcję, która wypisuje swój argument. Następnie możesz zastosować go do całego programu w sys.modules, chociaż łatwiejszym sposobem jest użycie sys.set_trace.
aoeu256,
@ aoeu256 Nie znam możliwości środowiska wykonawczego Lisp. Ale często używali makr, więc żaden normalny programista Lisp nie mógł odczytać kodu; Prawdopodobnie próba wykonania „prostych” czynności w środowisku wykonawczym nie może działać, ponieważ makra zmieniają wszystko w Lisp.
Tim Williscroft,
@TimWilliscroft Przed wykonaniem tego rodzaju czynności możesz poczekać, aż wszystkie makra zostaną rozwinięte. Emacs ma wiele skrótów klawiszowych, które pozwalają ci wbudowywać makra (i być może funkcje wbudowane).
aoeu256
4

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ć.

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.

Dodatkowo, ponieważ funkcje są zadeklarowane za pomocą typu funcname()..., bez znajomości typu będziesz musiał przeszukać każdą linię, w której funkcja jest wywoływana, ponieważ wiesz tylko, że funcnamew Pythonie i tym podobnych można po prostu wyszukać def funcnamelub function funcnameco dzieje się tylko raz , w deklaracji.

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:

fn funcname(a: i32) -> i32

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 należy dodać kilka wierszy kodu i ponownie skompilować wszystko, aby poznać zadeklarowany typ.

Każdy język może być interpretowany, a każdy język może mieć REPL.


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?

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.

Theodoros Chatzigiannakis
źródło
3

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.

gbjbaanb
źródło
Nie musisz robić wnioskowania typu statycznego (pylint), możesz dokonywać wnioskowania typu dynamicznego chrislaffra.blogspot.com/2016/12/…, co jest również wykonywane przez kompilator JIT PyPy. Istnieje również inna wersja wnioskowania typu dynamicznego, w której komputer losowo umieszcza fałszywe obiekty w argumentach i widzi, co powoduje błąd. Problem zatrzymania nie ma znaczenia w 99% przypadków, jeśli zajmiesz zbyt dużo czasu, po prostu zatrzymaj algorytm (w ten sposób Python obsługuje nieskończoną rekurencję, ma limit rekurencji, który można ustawić).
aoeu256,