Динамически типизированные языки не типизированы
Сравнивая системы типов , в динамической типизации нет преимуществ. Динамическая типизация - это особый случай статической типизации - это язык статической типизации, где каждая переменная имеет один и тот же тип. Вы можете добиться того же в Java (за исключением краткости), сделав каждую переменную типовой Object
, а значения «объект» - типом Map<String, Object>
:
void makeItBark(Object dog) {
Map<String, Object> dogMap = (Map<String, Object>) dog;
Runnable bark = (Runnable) dogMap.get("bark");
bark.run();
}
Таким образом, даже без размышлений вы можете добиться того же эффекта практически на любом языке со статической типизацией, за исключением синтаксического удобства. Вы не получаете никакой дополнительной выразительной силы; напротив, вы обладаете меньшей выразительной силой, потому что в динамически типизированном языке вы лишены возможности ограничивать переменные определенными типами.
Изготовление утиной коры на языке статической типизации
Кроме того, хороший статически типизированный язык позволит вам писать код, который работает с любым типом, у которого есть bark
операция. В Haskell это класс типов:
class Barkable a where
bark :: a -> unit
Это выражает ограничение, что для того, чтобы некоторый тип a
считался Barkable, должна существовать bark
функция, которая принимает значение этого типа и ничего не возвращает.
Затем вы можете написать универсальные функции в терминах Barkable
ограничения:
makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)
Это говорит о том, что makeItBark
будет работать для любого типа, удовлетворяющего Barkable
требованиям. Это может показаться похожим на interface
Java или C #, но у него есть одно большое преимущество - типы не должны заранее указывать , какие классы типов они удовлетворяют. Я могу сказать , что тип Duck
является Barkable
в любое время, даже если Duck
это тип третьей стороны , я не писал. На самом деле, не имеет значения, что автор Duck
не написал bark
функцию - я могу предоставить ее после факта, когда я говорю на языке, который Duck
удовлетворяет Barkable
:
instance Barkable Duck where
bark d = quack (punch (d))
makeItBark (aDuck)
Это говорит о том, что Duck
s может лаять, и их функция лая реализуется путем удара по утке перед тем, как ее крякнуть. С этим из пути мы можем призвать makeItBark
уток.
Standard ML
и OCaml
еще более гибки в том, что вы можете удовлетворить один и тот же класс типов несколькими способами. В этих языках я могу сказать, что целые числа можно упорядочить, используя обычное упорядочение, а затем развернуться и сказать, что они также можно упорядочить по делимости (например, 10 > 5
потому что 10 делится на 5). В Haskell вы можете создать экземпляр класса типов только один раз. (Это позволяет Haskell автоматически знать, что можно вызывать bark
утку; в SML или OCaml вы должны четко указывать, какую bark
функцию вы хотите, потому что их может быть несколько).
сжатость
Конечно, есть синтаксические различия. Код Python, который вы представили, гораздо более лаконичен, чем Java-эквивалент, который я написал. На практике эта краткость является большой частью привлекательности динамически типизированных языков. Но вывод типов позволяет вам писать код, который так же лаконичен для языков со статической типизацией, избавляя вас от необходимости явно писать типы каждой переменной. Язык со статической типизацией может также обеспечить встроенную поддержку динамической типизации, удаляя многословие всех операций приведения и преобразования карт (например, C # dynamic
).
Правильные, но плохо набранные программы
Чтобы быть справедливым, статическая типизация обязательно исключает некоторые программы, которые являются технически правильными, даже если средство проверки типов не может это проверить. Например:
if this_variable_is_always_true:
return "some string"
else:
return 6
В большинстве языков со статической типизацией это if
утверждение будет отклонено , хотя ветвь else никогда не появится. На практике кажется, что никто не использует этот тип кода - что-либо слишком умное для проверки типов, вероятно, заставит будущих сопровождающих вашего кода проклинать вас и ваших ближайших родственников. Например, кто-то успешно перевел 4 проекта с открытым исходным кодом на Python на Haskell, что означает, что они не делали ничего, что не смог бы скомпилировать хороший статически типизированный язык. Более того, компилятор обнаружил пару ошибок, связанных с типами, которые модульные тесты не улавливали.
Самый сильный аргумент, который я видел для динамической типизации, - это макросы Lisp, поскольку они позволяют произвольно расширять синтаксис языка. Однако Typed Racket - это статически типизированный диалект Lisp, в котором есть макросы, поэтому статическая типизация и макросы не являются взаимоисключающими, хотя, возможно, сложнее реализовать одновременно.
Яблоки и апельсины
Наконец, не забывайте, что различия в языках намного больше, чем просто в системе типов. До Java 8 любое функциональное программирование на Java было практически невозможно; простая лямбда потребует 4 строки стандартного кода анонимного класса. Java также не поддерживает литералы коллекций (например [1, 2, 3]
). Также могут быть различия в качестве и доступности инструментов (IDE, отладчиков), библиотек и поддержки сообщества. Когда кто-то заявляет, что он более продуктивен в Python или Ruby, чем в Java, необходимо учитывать это несоответствие функций. Существует разница между сравнением языков со всеми включенными батареями , языковыми ядрами и системами типов .
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof"))
, Этот аргумент даже не класс , это аноним с именем кортеж. Утиная печать («если она крякает как ...») позволяет создавать специальные интерфейсы с практически нулевыми ограничениями и без синтаксических накладных расходов. Вы можете сделать это на таком языке, как Java, но в итоге вы получите много грязных размышлений. Если функция в Java требует ArrayList и вы хотите присвоить ей другой тип коллекции, вы SOL. В питоне это даже не может всплыть.