Języki o typie dynamicznym są jednoznaczne
Porównując systemy typów , dynamiczne pisanie nie ma przewagi. Pisanie dynamiczne to szczególny przypadek pisania statycznego - jest to język o typie statycznym, w którym każda zmienna ma ten sam typ. To samo można osiągnąć w Javie (bez zwięzłości), ustawiając każdą zmienną na typ Object
i ustawiając wartości „obiektowe” na typ Map<String, Object>
:
void makeItBark(Object dog) {
Map<String, Object> dogMap = (Map<String, Object>) dog;
Runnable bark = (Runnable) dogMap.get("bark");
bark.run();
}
Tak więc, nawet bez refleksji, możesz osiągnąć ten sam efekt w prawie każdym statycznie wpisanym języku, pomijając wygodę składniową. Nie dostajesz żadnej dodatkowej mocy ekspresji; przeciwnie, masz mniej siłę wyrazu, ponieważ w dynamicznie wpisywanych języka, jesteś zaprzeczyć zdolność do ograniczania pewnych typów zmiennych.
Robienie kaczki z kory statycznie pisanym językiem
Co więcej, dobry język o typie statycznym pozwoli ci pisać kod, który działa z każdym typem, który ma bark
operację. W Haskell jest to klasa typu:
class Barkable a where
bark :: a -> unit
Wyraża to ograniczenie, że aby dany typ a
mógł zostać uznany za barkalny, musi istnieć bark
funkcja, która przyjmuje wartość tego typu i nic nie zwraca.
Następnie możesz napisać funkcje ogólne pod względem Barkable
ograniczenia:
makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)
Mówi to, że makeItBark
będzie działać na każdy typ spełniający Barkable
wymagania. Może się to wydawać podobne do interface
języka Java lub C #, ale ma jedną wielką zaletę - typy nie muszą z góry określać klas typów, które spełniają. Mogę powiedzieć, że ten typ Duck
jest Barkable
w dowolnym momencie, nawet jeśli Duck
nie napisałem tego typu strony trzeciej. W rzeczywistości nie ma znaczenia, że autor Duck
nie napisał bark
funkcji - mogę podać ją później, gdy powiem językowi, który Duck
spełnia Barkable
:
instance Barkable Duck where
bark d = quack (punch (d))
makeItBark (aDuck)
To mówi, że Duck
s mogą szczekać, a ich funkcja szczekania jest realizowana przez uderzenie kaczki przed jej kwakaniem. Dzięki temu możemy wezwać makeItBark
kaczki.
Standard ML
i OCaml
są jeszcze bardziej elastyczne, ponieważ możesz zaspokoić tę samą klasę typów na więcej niż jeden sposób. W tych językach mogę powiedzieć, że liczby całkowite można zamówić za pomocą konwencjonalnego porządku, a następnie odwrócić się i powiedzieć, że można je również uporządkować według podzielności (np. 10 > 5
Ponieważ 10 jest podzielne przez 5). W Haskell możesz utworzyć instancję klasy tylko raz. (Dzięki temu Haskell automatycznie wie, że można zadzwonić bark
do kaczki; w SML lub OCaml musisz wyraźnie określić, której bark
funkcji chcesz, ponieważ może być więcej niż jedna).
Zwięzłość
Oczywiście istnieją różnice składniowe. Przedstawiony kod Pythona jest o wiele bardziej zwięzły niż napisany przeze mnie odpowiednik Java. W praktyce zwięzłość ta jest dużą częścią uroku dynamicznie pisanych języków. Jednak wnioskowanie o typach pozwala pisać kod, który jest równie zwięzły w językach o typie statycznym, dzięki czemu nie musisz jawnie pisać typów każdej zmiennej. Język o typie statycznym może również zapewniać natywną obsługę dynamicznego pisania, usuwając szczegółowość wszystkich operacji rzutowania i mapowania (np. C # dynamic
).
Prawidłowe, ale źle napisane programy
Aby być sprawiedliwym, wpisywanie statyczne niekoniecznie wyklucza niektóre programy, które są technicznie poprawne, nawet jeśli moduł sprawdzania typów nie może tego zweryfikować. Na przykład:
if this_variable_is_always_true:
return "some string"
else:
return 6
Większość języków o typie statycznym odrzuciłaby to if
oświadczenie, nawet jeśli gałąź else nigdy się nie pojawi. W praktyce wydaje się, że nikt nie korzysta z tego typu kodu - coś zbyt sprytnego dla sprawdzania typów prawdopodobnie sprawi, że przyszli opiekunowie twojego kodu będą przeklinać ciebie i twoich najbliższych. W tym przypadku ktoś z powodzeniem przetłumaczył 4 projekty Pythona o otwartym kodzie źródłowym na Haskell, co oznacza, że nie robili niczego, czego nie byłby w stanie skompilować dobry język o typie statycznym. Co więcej, kompilator znalazł kilka błędów związanych z typem, których nie wykryły testy jednostkowe.
Najsilniejszym argumentem, jaki widziałem dla dynamicznego pisania, są makra Lispa, ponieważ pozwalają one dowolnie rozszerzyć składnię języka. Jednak Typed Racket to statycznie wpisany dialekt Lisp, który ma makra, więc wydaje się, że pisanie statyczne i makra nie wykluczają się wzajemnie, choć być może trudniej jest je jednocześnie wdrożyć.
Jabłka i Pomarańcze
Na koniec nie zapominaj, że istnieją większe różnice w językach niż tylko ich system typów. Przed Javą 8 wykonywanie jakiegokolwiek programowania funkcjonalnego w Javie było praktycznie niemożliwe; prosta lambda wymagałaby 4 wierszy anonimowego kodu klasy. Java również nie obsługuje literałów kolekcji (np [1, 2, 3]
.). Mogą również występować różnice w jakości i dostępności narzędzi (IDE, debuggery), bibliotek i wsparcia społeczności. Jeśli ktoś twierdzi, że jest bardziej produktywny w Pythonie lub Ruby niż Java, należy wziąć pod uwagę tę rozbieżność funkcji. Istnieje różnica między porównywaniem języków ze wszystkimi dołączonymi akumulatorami , rdzeniami językowymi i systemami typów .
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof"))
. Ten argument nie jest nawet klasą , to anonimowa krotka. Wpisywanie kaczki („jeśli kwaknie jak ...”) pozwala ci na tworzenie interfejsów ad hoc z zasadniczo zerowymi ograniczeniami i bez narzutu składniowego. Możesz to zrobić w języku takim jak Java, ale kończy się to nieporęcznymi refleksjami. Jeśli funkcja w Javie wymaga ArrayList i chcesz nadać jej inny typ kolekcji, jesteś SOL. W pythonie nawet to nie może się pojawić.