Jakie funkcje umożliwia dynamiczne pisanie? [Zamknięte]

91

Używam Pythona od kilku dni i myślę, że rozumiem różnicę między pisaniem dynamicznym a statycznym. Nie rozumiem, w jakich okolicznościach byłoby to preferowane. Jest elastyczny i czytelny, ale kosztem większej liczby kontroli środowiska wykonawczego i dodatkowych wymaganych testów jednostkowych.

Oprócz niefunkcjonalnych kryteriów, takich jak elastyczność i czytelność, jakie są powody, aby wybierać dynamiczne pisanie? Co mogę zrobić z dynamicznym pisaniem, które inaczej nie jest możliwe? Jak myślisz, jaki konkretny przykład kodu ilustruje konkretną zaletę dynamicznego pisania?

Justin984
źródło
5
Teoretycznie nic nie można zrobić w żadnym z tych języków, o ile języki są Turing Complete . Bardziej interesujące jest dla mnie pytanie, co jest łatwe lub naturalne w jednym kontra drugim. Są rzeczy, które robię regularnie w Pythonie, których nawet nie wziąłbym pod uwagę w C ++, chociaż wiem, że jest w stanie.
Mark Ransom
28
Jak pisze Chris Smith w swoim znakomitym eseju. Co należy wiedzieć przed debatą na temat systemów typu : „Problem w tym przypadku polega na tym, że większość programistów ma ograniczone doświadczenie i nie wypróbowała wielu języków. Dla kontekstu tutaj, sześć lub siedem nie liczy się jako „dużo.” ... Dwie interesujące konsekwencje tego: (1) Wielu programistów używa bardzo słabych języków o typie statycznym. (2) Wielu programistów bardzo słabo używa języków o typie dynamicznym. ”
Daniel Pryden,
3
@suslik: Jeśli prymitywy językowe mają nonsensowne typy, to oczywiście możesz robić nonsensowne rzeczy z typami. Nie ma to nic wspólnego z różnicą między pisaniem statycznym a dynamicznym.
Jon Purdy,
10
@CzarekTomczak: Tak, jest to cecha niektórych dynamicznie pisanych języków. Ale możliwe jest modyfikowanie języka o typie statycznym w czasie wykonywania. Na przykład Visual Studio pozwala przepisać kod C #, gdy jesteś w punkcie przerwania w debuggerze, a nawet przewinąć wskaźnik instrukcji, aby ponownie uruchomić kod z nowymi zmianami. Jak zacytowałem Chrisa Smitha w moim innym komentarzu: „Wielu programistów używa bardzo słabych języków o typie statycznym” - nie oceniaj wszystkich języków o typie statycznym według tych, które znasz.
Daniel Pryden,
11
@WarrenP: Twierdzisz, że „systemy typu dynamicznego zmniejszają ilość dodatkowego crufta, który muszę wpisać” - ale porównujesz Pythona do C ++. To nie jest sprawiedliwe porównanie: oczywiście C ++ jest bardziej gadatliwy niż Python, ale nie wynika to z różnicy w ich systemach typów, lecz z powodu różnicy w gramatyce. Jeśli chcesz tylko zmniejszyć liczbę znaków w źródle programu, naucz się J lub APL: Gwarantuję, że będą krótsze. Bardziej sprawiedliwym porównaniem byłoby porównanie Pythona z Haskellem. (Dla
przypomnienia

Odpowiedzi:

50

Ponieważ poprosiłeś o konkretny przykład, dam ci jeden.

Massive ORM Roba Conery'ego to 400 linii kodu. Jest tak mały, ponieważ Rob jest w stanie mapować tabele SQL i zapewniać wyniki obiektów bez konieczności posiadania wielu typów statycznych do dublowania tabel SQL. Dokonuje się tego za pomocą dynamictypu danych w języku C #. Strona Roba szczegółowo opisuje ten proces, ale wydaje się jasne, że w tym konkretnym przypadku użycia dynamiczne pisanie jest w dużej mierze odpowiedzialne za zwięzłość kodu.

Porównaj z Dapper Sama Saffrona , który wykorzystuje typy statyczne; SQLMapperklasa sama wynosi 3000 linii kodu.

Pamiętaj, że obowiązują zwykłe wyłączenia odpowiedzialności, a przebieg może się różnić; Dapper ma inne cele niż Massive. Podkreślam to jako przykład czegoś, co można zrobić w 400 liniach kodu, co prawdopodobnie nie byłoby możliwe bez dynamicznego pisania.


Pisanie dynamiczne umożliwia odroczenie decyzji dotyczących typu w czasie wykonywania. To wszystko.

Niezależnie od tego, czy używasz języka z dynamicznym pisaniem, czy ze statycznym, twój wybór tekstu musi być rozsądny. Nie dodasz dwóch ciągów razem i nie oczekujesz odpowiedzi numerycznej, chyba że ciągi zawierają dane liczbowe, a jeśli nie, otrzymasz nieoczekiwane wyniki. Język o typie statycznym nie pozwoli ci tego zrobić w pierwszej kolejności.

Zwolennicy języków o typie statycznym zwracają uwagę, że kompilator może w znacznym stopniu wykonać „sprawdzanie poprawności” kodu w czasie kompilacji, zanim zostanie wykonany pojedynczy wiersz. To jest Dobra Rzecz ™.

C # zawiera dynamicsłowo kluczowe, które pozwala ci odroczyć decyzję typu uruchomieniowego bez utraty korzyści bezpieczeństwa statycznego w pozostałej części kodu. Typ inferencji ( var) eliminuje wiele problemów związanych z pisaniem w języku o typie statycznym, eliminując potrzebę zawsze jawnego deklarowania typów.


Dynamiczne języki wydają się faworyzować bardziej interaktywne, bezpośrednie podejście do programowania. Nikt nie oczekuje, że będziesz musiał napisać klasę i przejść przez cykl kompilacji, aby wpisać trochę kodu Lisp i zobaczyć, jak się wykonuje. Ale właśnie tego mam się spodziewać w języku C #.

Robert Harvey
źródło
22
Gdybym dodał dwa ciągi liczbowe razem, nadal nie spodziewałbym się wyniku liczbowego.
pdr
22
@Robert Zgadzam się z większością twojej odpowiedzi. Należy jednak pamiętać, że istnieją języki o typie statycznym z interaktywnymi pętlami odczytu-odczytu-ewaluacji, takie jak Scala i Haskell. Być może C # nie jest językiem szczególnie interaktywnym.
Andres F.,
14
Musisz nauczyć mnie Haskell.
Robert Harvey
7
@RobertHarvey: Możesz być zaskoczony / pod wrażeniem F #, jeśli jeszcze tego nie próbowałeś. Otrzymujesz pełne bezpieczeństwo (w czasie kompilacji), które zwykle uzyskujesz w języku .NET, z tym wyjątkiem, że rzadko musisz deklarować jakiekolwiek typy. Wnioskowanie typu w języku F # wykracza poza to, co jest dostępne / działa w języku C #. Ponadto: podobnie do tego, co wskazują Andres i Daniel, F # Interactive jest częścią Visual Studio ...
Steven Evers,
8
„Nie dodasz dwóch ciągów razem i nie oczekujesz odpowiedzi liczbowej, chyba że ciągi te zawierają dane liczbowe, a jeśli nie, otrzymasz nieoczekiwane wyniki” przepraszam, nie ma to nic wspólnego z typowaniem dynamicznym vs. , to jest mocne vs. słabe pisanie.
vartec,
26

Zwroty takie jak „pisanie statyczne” i „pisanie dynamiczne” są często rzucane, a ludzie używają subtelnie różnych definicji, więc zacznijmy od wyjaśnienia, co mamy na myśli.

Rozważ język, który ma typy statyczne sprawdzane podczas kompilacji. Ale powiedzmy, że błąd typu generuje tylko ostrzeżenie nie kończące się śmiercią, aw czasie wykonywania wszystko jest zapisywane na maszynie. Te typy statyczne służą wyłącznie wygodzie programisty i nie wpływają na codegen. To pokazuje, że pisanie statyczne samo w sobie nie narzuca żadnych ograniczeń i nie wyklucza się wzajemnie z pisaniem dynamicznym. (Cel C jest podobny do tego.)

Ale większość systemów typu statycznego nie zachowuje się w ten sposób. Istnieją dwie wspólne właściwości systemów typu statycznego, które mogą nakładać ograniczenia:

Kompilator może odrzucić program zawierający błąd typu statycznego.

Jest to ograniczenie, ponieważ wiele programów typu bezpiecznego musi zawierać błąd typu statycznego.

Na przykład mam skrypt w języku Python, który musi działać zarówno jako Python 2, jak i Python 3. Niektóre funkcje zmieniły typy parametrów między Python 2 i 3, więc mam taki kod:

if sys.version_info[0] == 2:
    wfile.write(txt)
else:
    wfile.write(bytes(txt, 'utf-8'))

Sprawdzanie statycznego typu Python 2 odrzuciłoby kod Python 3 (i odwrotnie), nawet jeśli nigdy nie zostałoby wykonane. Bezpieczny program mojego typu zawiera błąd typu statycznego.

Jako kolejny przykład rozważmy komputer Mac, który chce działać w systemie OS X 10.6, ale skorzystaj z nowych funkcji w wersji 10.7. Metody 10.7 mogą, ale nie muszą istnieć w czasie wykonywania, i wykrycie ich zależy ode mnie, programisty. Sprawdzanie typu statycznego jest zmuszone albo odrzucić mój program, aby zapewnić bezpieczeństwo typu, albo zaakceptować program, wraz z możliwością spowodowania błędu typu (brak funkcji) w czasie wykonywania.

Statyczne sprawdzanie typu zakłada, że ​​środowisko wykonawcze jest odpowiednio opisane w informacjach o czasie kompilacji. Ale przewidywanie przyszłości jest niebezpieczne!

Oto jeszcze jedno ograniczenie:

Kompilator może generować kod, który zakłada, że ​​typ środowiska wykonawczego jest typem statycznym.

Zakładanie, że typy statyczne są „prawidłowe”, daje wiele możliwości optymalizacji, ale te optymalizacje mogą być ograniczające. Dobrym przykładem są obiekty proxy, np. Zdalne. Załóżmy, że chcesz mieć lokalny obiekt proxy, który przekazuje wywołania metod do rzeczywistego obiektu w innym procesie. Byłoby miło, gdyby serwer proxy był ogólny (aby mógł maskować się jako dowolny obiekt) i przezroczysty (aby istniejący kod nie musiał wiedzieć, że rozmawia z serwerem proxy). Ale aby to zrobić, kompilator nie może wygenerować kodu, który zakłada, że ​​typy statyczne są poprawne, np. Przez statyczne wstawianie wywołań metod, ponieważ to się nie powiedzie, jeśli obiekt jest proxy.

Przykłady takiego zdalnego działania w akcji to NSXPCConnection ObjC lub TransparentProxy w języku C # (którego implementacja wymagała kilku pesymizacji w środowisku wykonawczym - patrz tutaj dyskusja).

Gdy codegen nie jest zależny od typów statycznych i masz takie funkcje, jak przekazywanie wiadomości, możesz robić wiele fajnych rzeczy z obiektami proxy, debugowaniem itp.

To jest próbka niektórych rzeczy, które możesz zrobić, jeśli nie musisz spełniać wymagań sprawdzania typu. Ograniczenia nie są narzucane przez typy statyczne, ale przez wymuszone sprawdzanie typów statycznych.

śmieszne ryby
źródło
2
„Narzędzie do sprawdzania statycznego typu Python 2 odrzuciłoby kod Python 3 (i odwrotnie), nawet jeśli nigdy nie zostałoby wykonane. Bezpieczny program mojego typu zawiera błąd typu statycznego.” Brzmi jak to, czego naprawdę potrzebujesz, jest jakiś „statyczny jeśli”, gdzie kompilator / interpreter nawet nie widzi kodu, jeśli warunek jest fałszywy.
David Stone
@davidstone That is in c ++
Milind R
A Python 2 static type checker would reject the Python 3 code (and vice versa), even though it would never be executed. My type safe program contains a static type error. W dowolnym rozsądnym języku statycznym można to zrobić za pomocą IFDEFinstrukcji preprocesora typu, zachowując bezpieczeństwo typu w obu przypadkach.
Mason Wheeler,
1
@MasonWheeler, David No, sztuczki preprocesora i static_if są zbyt statyczne. W moim przykładzie użyłem Python2 i Python3, ale mogło to być tak proste jak AmazingModule2.0 i AmazingModule3.0, gdzie niektóre interfejsy zmieniały się między wersjami. Interfejs najwcześniej można poznać w czasie importowania modułu, co jest koniecznie w czasie wykonywania (przynajmniej jeśli masz ochotę na obsługę dynamicznego łączenia).
ridiculous_fish,
18

Zmienne typu kaczego są pierwszą rzeczą, o której wszyscy myślą, ale w większości przypadków te same korzyści można uzyskać przez wnioskowanie o typie statycznym.

Ale pisanie kaczek w dynamicznie tworzonych kolekcjach jest trudne do osiągnięcia w inny sposób:

>>> d = JSON.parse(foo)
>>> d['bar'][3]
12
>>> d['baz']['qux']
'quux'

Więc jaki typ JSON.parsepowraca? Słownik tablic liczb całkowitych lub słowników ciągów? Nie, nawet to nie jest wystarczająco ogólne.

JSON.parsemusi zwrócić jakąś „wartość wariantu”, która może być zerowa, bool, zmiennoprzecinkowa, łańcuch, tablica dowolnego z tych typów rekurencyjnie lub słownik od łańcucha do dowolnego z tych typów rekurencyjnie. Główne zalety pisania dynamicznego wynikają z posiadania tego rodzaju wariantów.

Jak dotąd jest to zaleta typów dynamicznych , a nie języków dynamicznie typowanych. Przyzwoity język statyczny może idealnie symulować każdy taki typ. (A nawet „złe” języki często mogą je symulować, łamiąc bezpieczeństwo pod maską i / lub wymagając niezgrabnej składni.)

Zaletą języków dynamicznie typowanych jest to, że takie typy nie mogą być wywnioskowane przez statyczne systemy wnioskowania typu. Musisz napisać ten typ jawnie. Ale w wielu takich przypadkach - w tym raz - kod opisujący typ jest dokładnie tak skomplikowany jak kod do parsowania / konstruowania obiektów bez opisywania typu, więc niekoniecznie jest to zaletą.

abarnert
źródło
21
Przykład parsowania JSON można łatwo obsłużyć statycznie za pomocą algebraicznego typu danych.
2
OK, moja odpowiedź nie była wystarczająco jasna; dzięki. Ta JSValue jest wyraźną definicją typu dynamicznego, dokładnie o czym mówiłem. Przydatne są te typy dynamiczne, a nie języki wymagające dynamicznego pisania. Nadal jednak istotne jest, że typy dynamiczne nie mogą być automatycznie generowane przez system wnioskowania typu rzeczywistego, podczas gdy większość typowych przykładów ludzie są trywialnie nie do zniesienia. Mam nadzieję, że nowa wersja lepiej to wyjaśnia.
abarnert
4
@MattFenwick Algebraiczne typy danych są praktycznie ograniczone do języków funkcjonalnych (w praktyce). Co z językami takimi jak Java i C #?
spirc
4
ADT istnieją w C / C ++ jako oznaczone związki. Nie dotyczy to tylko języków funkcjonalnych.
Clark Gaebel
2
@spirc możesz emulować ADT w klasycznym języku OO, używając wielu klas, które wszystkie wywodzą się ze wspólnego interfejsu, wywołań w czasie wykonywania getClass () lub GetType () oraz kontroli równości. Lub możesz użyć podwójnej wysyłki, ale myślę, że to się opłaca w C ++. Więc możesz mieć interfejs JSObject oraz klasy JSString, JSNumber, JSHash i JSArray. Potrzebny byłby wtedy trochę kodu, aby przekształcić tę „nietypową” strukturę danych w „typ aplikacji”. Ale prawdopodobnie chciałbyś to zrobić również w języku dynamicznym.
Daniel Yankowsky
12

Ponieważ każdy zdalnie praktyczny system typu statycznego jest poważnie ograniczony w porównaniu do języka programowania, którego dotyczy, nie może wyrazić wszystkich niezmienników, które kod mógłby sprawdzić w czasie wykonywania. Aby nie obchodzić gwarancji, które system stara się udzielić, decyduje się być konserwatywny i nie zezwala na przypadki użycia, które przejdą takie testy, ale nie można tego udowodnić (w systemie typów).

Dam przykład. Załóżmy, że wdrażasz prosty model danych do opisywania obiektów danych, ich zbiorów itp., Który jest statycznie wpisany w tym sensie, że jeśli model mówi, że atrybut xobiektu typu Foo zawiera liczbę całkowitą, musi zawsze zawierać liczbę całkowitą. Ponieważ jest to konstrukcja wykonawcza, nie można jej wpisać statycznie. Załóżmy, że przechowujesz dane opisane w plikach YAML. Tworzysz mapę skrótu (do późniejszego przekazania do biblioteki YAML), dostajesz xatrybut, przechowujesz go na mapie, dostajesz ten inny atrybut, który tak się składa, że ​​jest łańcuchem, ... poczekaj chwilę? Jaki jest the_map[some_key]teraz typ ? Cóż, strzelaj, wiemy, że tak some_keyjest 'x'i dlatego wynik musi być liczbą całkowitą, ale system typów nie może nawet zacząć o tym myśleć.

Niektóre aktywnie badane systemy typów mogą działać w tym konkretnym przykładzie, ale są one niezwykle skomplikowane (zarówno dla autorów kompilatorów do zaimplementowania, jak i dla programistów do uzasadnienia), szczególnie dla czegoś tak „prostego” (to znaczy, właśnie wyjaśniłem to w jednym ustęp).

Oczywiście, dzisiejszym rozwiązaniem jest boksowanie wszystkiego, a następnie rzutowanie (lub posiadanie wielu przesłoniętych metod, z których większość podnosi wyjątki „nie zaimplementowane”). Ale to nie jest statycznie wpisane, jest to włamanie do systemu typów w celu sprawdzenia typów w czasie wykonywania.

7043
źródło
Typy ogólne nie mają wymagań dotyczących boksu.
Robert Harvey
@RobertHarvey Tak. Nie rozmawiałem z boksowaniem w Javie C #, mówiłem o „zawinięciu w jakąś klasę opakowania, której jedynym celem jest reprezentowanie wartości T w podtypie U”. Polimorfizm parametryczny (co nazywacie typowym typowaniem) nie dotyczy jednak mojego przykładu. Jest to abstrakcja w czasie kompilacji konkretnych typów, ale potrzebujemy mechanizmu typowania w czasie wykonywania.
Warto zauważyć, że system typów Scali jest kompletny w Turingu. Tak więc systemy pisania mogą być mniej trywialne niż można sobie wyobrazić.
Andrea
@Andrea Celowo nie ograniczyłem mojego opisu do kompletności. Czy byłeś kiedyś zaprogramowany w tarczy Turinga? Czy próbowałeś zakodować te rzeczy pismem? W pewnym momencie staje się zbyt skomplikowane, aby było wykonalne.
@delnan Zgadzam się. Właśnie wskazałem, że systemy typów mogą robić dość skomplikowane rzeczy. Miałem wrażenie, że twoja odpowiedź oznaczała, że ​​system pisma może tylko trywialną weryfikację, ale przy drugim czytaniu nic takiego nie napisałeś!
Andrea
7

Nic nie można zrobić z dynamicznym pisaniem, czego nie można zrobić z pisaniem statycznym, ponieważ można zaimplementować pisanie dynamiczne na podstawie języka statycznego.

Krótki przykład w Haskell:

data Data = DString String | DInt Int | DDouble Double

-- defining a '+' operator here, with explicit promotion behavior
DString a + DString b = DString (a ++ b)
DString a + DInt b = DString (a ++ show b)
DString a + DDouble b = DString (a ++ show b)
DInt a + DString b = DString (show a ++ b)
DInt a + DInt b = DInt (a + b)
DInt a + DDouble b = DDouble (fromIntegral a + b)
DDouble a + DString b = DString (show a ++ b)
DDouble a + DInt b = DDouble (a + fromIntegral b)
DDouble a + DDouble b = DDouble (a + b)

Przy wystarczającej liczbie przypadków można wdrożyć dowolny system typu dynamicznego.

I odwrotnie, możesz także przetłumaczyć dowolny program o typie statycznym na równoważny program dynamiczny. Oczywiście stracilibyście wszystkie zapewnienia poprawności w czasie kompilacji zapewniane przez język typowany statycznie.

Edycja: Chciałem zachować prostotę, ale oto więcej szczegółów na temat modelu obiektowego

Funkcja przyjmuje listę danych jako argumenty i wykonuje obliczenia z efektami ubocznymi w ImplMonad i zwraca dane.

type Function = [Data] -> ImplMonad Data

DMember jest wartością elementu lub funkcją.

data DMember = DMemValue Data | DMemFunction Function

Rozszerz, Dataaby objąć obiekty i funkcje. Obiekty to listy nazwanych członków.

data Data = .... | DObject [(String, DMember)] | DFunction Function

Te typy statyczne są wystarczające do wdrożenia każdego systemu obiektów o typie dynamicznym, który znam.

NovaDenizen
źródło
To wcale nie jest to samo, ponieważ nie można dodawać nowych typów bez zmiany definicji Data.
Jed
5
W swoim przykładzie łączysz koncepcje pisania dynamicznego ze słabym pisaniem. Wpisywanie dynamiczne polega na operowaniu na nieznanych typach, nie definiowaniu listy dozwolonych typów i przeciążaniu operacji między nimi.
hcalves
2
@Jed Po zaimplementowaniu modelu obiektowego, podstawowych typów i operacji prymitywnych żadne inne prace przygotowawcze nie są konieczne. Możesz łatwo i automatycznie tłumaczyć programy z oryginalnego dynamicznego języka na ten dialekt.
NovaDenizen,
2
@hcalves Ponieważ chodzi ci o przeciążenie mojego kodu Haskell, podejrzewam, że nie masz właściwego pomysłu na temat jego semantyki. Tam zdefiniowałem nowy +operator, który łączy dwie Datawartości w inną Datawartość. Datareprezentuje standardowe wartości w systemie typu dynamicznego.
NovaDenizen
1
@Jed: Większość języków dynamicznych ma niewielki zestaw „prymitywnych” typów i jakiś indukcyjny sposób wprowadzania nowych wartości (struktury danych, takie jak listy). Schemat na przykład idzie dość daleko z niewiele więcej niż atomami, parami i wektorami. Powinieneś być w stanie zaimplementować je w taki sam sposób, jak resztę danego typu dynamicznego.
Tikhon Jelvis,
3

Membrany :

Membrana jest oplotem wokół całego wykresu obiektu, w przeciwieństwie do opaski tylko dla jednego obiektu. Zazwyczaj twórca membrany zaczyna owijać w membranę tylko jeden przedmiot. Kluczową ideą jest to, że każde odniesienie do obiektu, które przechodzi przez membranę, jest samoistnie owinięte w tę samą membranę.

wprowadź opis zdjęcia tutaj

Każdy typ jest zawijany przez typ, który ma ten sam interfejs, ale który przechwytuje wiadomości i zawija i rozpakowuje wartości podczas przechodzenia przez membranę. Jaki jest typ funkcji zawijania w twoim ulubionym języku wpisywanym statycznie? Być może Haskell ma typ dla tych funkcji, ale większość języków o typie statycznym tego nie robi lub używają Object → Object, skutecznie rezygnując z odpowiedzialności za sprawdzanie typów.

Mike Samuel
źródło
4
Tak, Haskell rzeczywiście może to zrobić, używając typów egzystencjalnych. Jeśli masz jakąś klasę typu Foo, możesz utworzyć opakowanie dowolnego typu, tworząc instancję tego interfejsu. class Foo a where ... data Wrapper = forall a. Foo a => Wrapper a
Jake McArthur
@JakeMcArthur, dzięki za wyjaśnienie. To kolejny powód, dla którego usiadłem i uczyłem się Haskella.
Mike Samuel
2
Twoja membrana jest „interfejsem”, a typy obiektów są „typowo egzystencjalne” - to znaczy wiemy, że istnieją pod interfejsem, ale to wszystko, co wiemy. Egzystencjalne typy abstrakcji danych są znane od lat 80. Dobry odnośnik to cs.cmu.edu/~rwh/plbook/book.pdf rozdział 21.1
Don Stewart
@DonStewart. Czy zatem klasy proxy Java są mechanizmem typu egzystencjalnego? Jednym z miejsc, w których membrany stają się trudne, są języki z systemami typów nominalnych, które mają nazwy typów betonu widoczne poza definicją tego typu. Na przykład nie można zawijać, Stringponieważ jest to konkretny typ w Javie. Smalltalk nie ma tego problemu, ponieważ nie próbuje pisać #doesNotUnderstand.
Mike Samuel,
1

Jak ktoś wspomniał, teoretycznie niewiele można zrobić z dynamicznym pisaniem, czego nie można zrobić z pisaniem statycznym, gdybyś sam wdrożył niektóre mechanizmy. Większość języków zapewnia mechanizmy relaksacji typów, które wspierają elastyczność typów, takie jak wskaźniki pustki i typ obiektu głównego lub pusty interfejs.

Lepszym pytaniem jest, dlaczego dynamiczne pisanie jest bardziej odpowiednie i bardziej odpowiednie w określonych sytuacjach i problemach.

Najpierw zdefiniujmy

Podmiot - potrzebowałbym ogólnego pojęcia o pewnym bycie w kodzie. Może to być wszystko - od liczby pierwotnej po złożone dane.

Zachowanie - powiedzmy, że nasza istota ma pewien stan i zestaw metod, które pozwalają światu na instruowanie istoty do pewnych reakcji. Nazwijmy stan + interfejs tego bytu jego zachowaniem. Jedna jednostka może mieć więcej niż jedno zachowanie połączone w określony sposób przez narzędzia zapewniane przez język.

Definicje bytów i ich zachowań - każdy język zapewnia pewne abstrakcje, które pomagają zdefiniować zachowania (zestaw metod + stan wewnętrzny) niektórych bytów w programie. Możesz przypisać nazwę do tych zachowań i powiedzieć, że wszystkie wystąpienia tego zachowania są określonego typu .

Prawdopodobnie jest to coś, co nie jest tak obce. I jak powiedziałeś, zrozumiałeś różnicę, ale nadal. Prawdopodobnie niepełne i najdokładniejsze wyjaśnienie, ale mam nadzieję, że będzie wystarczająco fajna, aby przynieść jakąś wartość :)

Wpisywanie statyczne - zachowanie wszystkich bytów w twoim programie jest sprawdzane w czasie kompilacji, przed uruchomieniem kodu. Oznacza to, że jeśli na przykład chcesz, aby twoja encja typu Osoba miała zachowanie Magika, musisz zdefiniować encję MagicianPerson i nadać jej zachowania magowi typu throwMagic (). Jeśli podasz kod, omyłkowo powiesz to zwykłemu kompilatorowi Person.throwMagic ()"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".

Pisanie dynamiczne - w środowiskach pisania dynamicznego dostępne zachowania jednostek nie są sprawdzane, dopóki naprawdę nie spróbujesz zrobić czegoś z określoną jednostką. Uruchomienie kodu Ruby, który prosi Person.throwMagic (), nie zostanie przechwycone, dopóki twój kod nie pojawi się naprawdę. Brzmi frustrująco, prawda? Ale to także brzmi rewelacyjnie. Na podstawie tej właściwości możesz robić ciekawe rzeczy. Załóżmy, że projektujesz grę, w której wszystko może zmienić się w Maga i nie wiesz, kto to będzie, dopóki nie dojdziesz do określonego punktu w kodzie. A potem przychodzi Żaba i mówiszHeyYouConcreteInstanceOfFrog.include Magici od tego momentu ta Żaba staje się jedną konkretną Żabą, która ma magiczne moce. Inne żaby, wciąż nie. Widzisz, w statycznych językach do pisania, musiałbyś zdefiniować tę relację za pomocą standardowej kombinacji kombinacji zachowań (takich jak implementacja interfejsu). W dynamicznym języku pisania możesz to zrobić w czasie wykonywania i nikogo to nie obchodzi.

Większość dynamicznych języków pisania ma mechanizmy zapewniające ogólne zachowanie, które przechwytuje każdy komunikat przekazywany do ich interfejsu. Na przykład Ruby method_missingi PHP, __calljeśli dobrze pamiętam. Oznacza to, że możesz wykonywać dowolne ciekawe rzeczy w czasie wykonywania programu i podejmować decyzje dotyczące typu na podstawie bieżącego stanu programu. Daje to narzędzia do modelowania problemu, które są znacznie bardziej elastyczne niż, powiedzmy, konserwatywny statyczny język programowania, taki jak Java.

ivanjovanovic
źródło