Я использую python уже несколько дней и думаю, что понимаю разницу между динамической и статической типизацией. Что я не понимаю, так это при каких обстоятельствах это будет предпочтительнее. Он гибкий и читаемый, но за счет дополнительных проверок во время выполнения и дополнительных необходимых модульных тестов
Помимо нефункциональных критериев, таких как гибкость и удобочитаемость, какие есть причины выбирать динамическую типизацию? Что я могу сделать с динамической типизацией, которая иначе невозможна? Какой конкретный пример кода, о котором вы можете подумать, иллюстрирует конкретное преимущество динамической типизации?
dynamic-typing
static-typing
Justin984
источник
источник
Ответы:
Поскольку вы попросили конкретный пример, я дам вам один.
Роб Конери в Массивная ORM 400 строк кода. Это так мало, потому что Роб может отображать таблицы SQL и предоставлять результаты объекта, не требуя большого количества статических типов для зеркалирования таблиц SQL. Это достигается с помощью
dynamic
типа данных в C #. На веб-странице Роба подробно описан этот процесс, но очевидно, что в данном конкретном случае динамическая типизация в значительной степени отвечает за краткость кода.Сравните с Dapper Сэма Шафрона , который использует статические типы;
SQLMapper
в одиночку класс 3000 строк кода.Обратите внимание, что применяются обычные заявления об отказе от ответственности, и ваш пробег может отличаться; У Даппера другие цели, чем у Массива. Я просто указываю на это в качестве примера того, что вы можете сделать в 400 строках кода, что, вероятно, было бы невозможно без динамической типизации.
Динамическая типизация позволяет отложить принятие решений о типе до времени выполнения. Вот и все.
Независимо от того, используете ли вы динамически типизированный или статически типизированный язык, ваш выбор типов должен быть разумным. Вы не собираетесь добавлять две строки вместе и ожидать числового ответа, если строки не содержат числовых данных, а если их нет, вы получите неожиданные результаты. Статически типизированный язык не позволит вам сделать это в первую очередь.
Сторонники языков статического типа указывают, что компилятор может выполнить значительный объем «проверки работоспособности» вашего кода во время компиляции перед выполнением одной строки. Это хорошая вещь ™.
C # имеет
dynamic
ключевое слово, которое позволяет вам отложить решение о типе до времени выполнения, не теряя преимущества статической безопасности типов в остальной части вашего кода. Тип inference (var
) устраняет большую часть трудностей написания на статически типизированном языке, устраняя необходимость всегда явно объявлять типы.Динамические языки предпочитают более интерактивный, непосредственный подход к программированию. Никто не ожидает, что вам придется написать класс и пройти цикл компиляции, чтобы набрать немного кода на Лиспе и посмотреть, как он выполняется. Тем не менее, это именно то, что я ожидал сделать в C #.
источник
Фразы типа «статическая типизация» и «динамическая типизация» часто встречаются, и люди, как правило, используют слегка разные определения, поэтому давайте начнем с пояснения того, что мы имеем в виду.
Рассмотрим язык со статическими типами, которые проверяются во время компиляции. Но допустим, что ошибка типа генерирует только нефатальное предупреждение, а во время выполнения все типизируется по типу утки. Эти статические типы предназначены только для удобства программиста и не влияют на codegen. Это показывает, что статическая типизация сама по себе не накладывает никаких ограничений и не является взаимоисключающей с динамической типизацией. (Objective-C очень похож на это.)
Но большинство систем статического типа не ведут себя таким образом. Существует два общих свойства систем статического типа, которые могут накладывать ограничения:
Компилятор может отклонить программу, содержащую ошибку статического типа.
Это ограничение, потому что многие типобезопасные программы обязательно содержат статическую ошибку типа.
Например, у меня есть скрипт Python, который должен запускаться как Python 2 и Python 3. Некоторые функции изменили свои типы параметров между Python 2 и 3, поэтому у меня есть такой код:
Средство проверки статического типа Python 2 будет отклонять код Python 3 (и наоборот), даже если он никогда не будет выполнен. Моя типобезопасная программа содержит статическую ошибку типа.
В качестве другого примера рассмотрим программу для Mac, которая хочет работать на OS X 10.6, но использовать преимущества новых функций в 10.7. Методы 10.7 могут существовать или не существовать во время выполнения, и я, программист, могу их обнаружить. Средство проверки статического типа вынуждено либо отклонить мою программу, чтобы обеспечить безопасность типов, либо принять программу вместе с возможностью создания ошибки типа (функция отсутствует) во время выполнения.
Проверка статического типа предполагает, что среда выполнения адекватно описывается информацией времени компиляции. Но предсказывать будущее опасно!
Вот еще одно ограничение:
Компилятор может генерировать код, который предполагает, что тип среды выполнения является статическим типом.
Предполагая, что статические типы являются «правильными», предоставляет много возможностей для оптимизации, но эти оптимизации могут быть ограничивающими. Хорошим примером являются прокси-объекты, например, удаленное взаимодействие. Скажем, вы хотите иметь локальный прокси-объект, который перенаправляет вызовы методов к реальному объекту в другом процессе. Было бы хорошо, если бы прокси был универсальным (чтобы он мог маскироваться под любой объект) и прозрачным (чтобы существующий код не знал, что он разговаривает с прокси). Но для этого компилятор не может сгенерировать код, который предполагает, что статические типы являются правильными, например, путем статически встроенных вызовов методов, потому что это не удастся, если объект на самом деле является прокси.
Примеры такого удаленного взаимодействия в действии включают NSXPCConnection ObjC или TransparentProxy C # (реализация которого потребовала нескольких пессимизаций во время выполнения - см. Здесь для обсуждения).
Когда codegen не зависит от статических типов, и у вас есть такие возможности, как пересылка сообщений, вы можете делать много интересных вещей с прокси-объектами, отладкой и т. Д.
Вот пример некоторых вещей, которые вы можете сделать, если вам не требуется выполнять проверку типов. Ограничения накладываются не статическими типами, а принудительной статической проверкой типов.
источник
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.
На любом приемлемом статическом языке вы можете сделать это с помощьюIFDEF
оператора препроцессора типа, сохраняя при этом безопасность типов в обоих случаях.Переменные типа «утка» - это первое, о чем все думают, но в большинстве случаев вы можете получить те же преимущества благодаря логическому выводу типа.
Но утка, набирающая в динамически созданных коллекциях, трудно достигнуть любым другим способом:
Итак, какой тип
JSON.parse
возвращает? Словарь массивов целых или словарей строк? Нет, даже этого недостаточно.JSON.parse
должен возвращать какое-то «вариантное значение», которое может быть нулевым, bool, float, string, массивом любого из этих типов рекурсивно или словарем из строки в любой из этих типов рекурсивно. Основные сильные стороны динамической типизации проистекают из наличия таких типов вариантов.Пока что это преимущество динамических типов , а не динамически типизированных языков. Приличный статический язык может идеально имитировать любой такой тип. (И даже «плохие» языки часто могут имитировать их, нарушая безопасность типов и / или требуя неуклюжего синтаксиса доступа.)
Преимущество языков с динамической типизацией заключается в том, что такие типы не могут быть выведены с помощью систем логического вывода типов. Вы должны написать тип явно. Но во многих таких случаях, в том числе и один раз, код для описания типа так же сложен, как и код для анализа / конструирования объектов без описания типа, так что это не обязательно является преимуществом.
источник
Поскольку каждая удаленная практическая система статических типов строго ограничена по сравнению с языком программирования, с которым она связана, она не может выразить все инварианты, которые код может проверить во время выполнения. Чтобы не обойти гарантии, которые пытается дать система типов, она предпочитает быть консервативными и не допускать использования, которые прошли бы эти проверки, но не могут (в системе типов) доказать это.
Я приведу пример. Предположим, что вы реализуете простую модель данных для описания объектов данных, их коллекций и т. Д., Которая статически типизирована в том смысле, что, если модель говорит, что атрибут
x
объекта типа Foo содержит целое число, он всегда должен содержать целое число. Поскольку это конструкция времени выполнения, вы не можете набирать ее статически. Предположим, вы храните данные, описанные в файлах YAML. Вы создаете хеш-карту (которая будет передана в библиотеку YAML позже), получаетеx
атрибут, сохраняете его на карте, получаете другой атрибут, который так же бывает строкой, ... держите секунду? Какой типthe_map[some_key]
сейчас? Хорошо, стреляйте, мы знаем, чтоsome_key
это так,'x'
и поэтому результат должен быть целым числом, но система типов не может даже начать рассуждать об этом.Некоторые активно исследуемые системы типов могут работать для этого конкретного примера, но они чрезвычайно сложны (как для разработчиков компиляторов, так и для программистов), особенно для чего-то такого «простого» (я имею в виду, я только что объяснил это в одном параграф).
Конечно, сегодняшнее решение состоит в том, чтобы собрать все воедино и затем преобразовать (или иметь кучу переопределенных методов, большинство из которых вызывают «не реализованные» исключения). Но это не статически типизировано, это хакерская система типов для проверки типов во время выполнения.
источник
С динамической типизацией вы ничего не можете поделать, как со статической типизацией, так как вы можете реализовать динамическую типизацию поверх статически типизированного языка.
Краткий пример на Haskell:
В достаточном количестве случаев вы можете реализовать любую данную динамическую систему типов.
И наоборот, вы также можете перевести любую статически типизированную программу в эквивалентную динамическую. Конечно, вы потеряете все гарантии правильности во время компиляции, которые предоставляет статически типизированный язык.
Изменить: я хотел бы сделать это простым, но вот больше деталей об объектной модели
Функция принимает список данных в качестве аргументов и выполняет вычисления с побочными эффектами в ImplMonad и возвращает данные.
DMember
является либо значением члена, либо функцией.Расширьте,
Data
чтобы включить объекты и функции. Объекты - это списки именованных членов.Эти статические типы достаточны для реализации каждой динамически типизированной объектной системы, с которой я знаком.
источник
Data
.+
оператор, который объединяет дваData
значения в другоеData
значение.Data
представляет стандартные значения в динамической системе типов.Мембраны :
Каждый тип упакован в тип, который имеет тот же интерфейс, но который перехватывает сообщения, а также переносит и разворачивает значения, когда они пересекают мембрану. Каков тип функции переноса в вашем любимом статически типизированном языке? Возможно, у Haskell есть тип для этих функций, но большинство статически типизированных языков не имеют, или они в конечном итоге используют Object → Object, эффективно отрекаясь от своей ответственности как контролеров типов.
источник
class Foo a where ...
data Wrapper = forall a. Foo a => Wrapper a
String
так как это конкретный тип в Java. Smalltalk не имеет этой проблемы, потому что он не пытается печатать#doesNotUnderstand
.Как кто-то упоминал, в теории вы ничего не можете сделать с динамической типизацией, которую вы не могли бы сделать со статической типизацией, если бы вы реализовали определенные механизмы самостоятельно. Большинство языков предоставляют механизмы ослабления типов для поддержки гибкости типов, такие как указатели типа void и тип корневого объекта или пустой интерфейс.
Лучший вопрос - почему динамическая типизация более подходит и более уместна в определенных ситуациях и проблемах?
Во-первых, давайте определимся
Сущность - мне нужно общее понятие некоторой сущности в коде. Это может быть что угодно, от простого числа до сложных данных.
Поведение - допустим, у нашей сущности есть некоторое состояние и набор методов, которые позволяют внешнему миру инструктировать сущность для определенных реакций. Давайте назовем состояние + интерфейс этого объекта его поведением. Одна сущность может иметь более чем одно поведение, объединенное определенным образом с помощью языка инструментов.
Определения сущностей и их поведения - каждый язык предоставляет некоторые средства абстракций, которые помогают вам определять поведение (набор методов + внутреннее состояние) определенных сущностей в программе. Вы можете назначить имя для этих поведений и сказать, что все экземпляры с таким поведением имеют определенный тип .
Вероятно, это не то, что незнакомо. И, как вы сказали, вы поняли разницу, но все же. Вероятно, не полное и самое точное объяснение, но я надеюсь, что достаточно веселья, чтобы принести некоторую ценность :)
Статическая типизация - поведение всех объектов в вашей программе проверяется во время компиляции, прежде чем код будет запущен. Это означает, что если вы хотите, например, чтобы ваша сущность типа Person имела поведение (чтобы вести себя как) Magician, вам нужно было бы определить сущность MagicianPerson и присвоить ей поведение мага, такого как throwMagic (). Если вы в своем коде, ошибочно скажете обычному компилятору Person.throwMagic () скажет вам
"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".
Динамическая типизация - в средах динамической типизации доступное поведение объектов не проверяется, пока вы действительно не попытаетесь что-то сделать с определенным объектом. Выполнение кода Ruby, который запрашивает Person.throwMagic (), не будет перехвачено, пока ваш код действительно не появится там. Это звучит расстраивающе, не так ли? Но это звучит откровенно. На основании этого свойства вы можете делать интересные вещи. Например, предположим, вы разрабатываете игру, в которой все может превратиться в Волшебника, и вы на самом деле не знаете, кто это будет, пока не дойдете до определенного момента в коде. А потом приходит Лягушка и ты говоришь
HeyYouConcreteInstanceOfFrog.include Magic
и с тех пор эта Лягушка становится одной конкретной Лягушкой, обладающей магическими способностями. Других лягушек до сих пор нет. Видите ли, в языках статической типизации вы должны были бы определить это отношение с помощью некоторого стандартного среднего значения комбинации поведения (например, реализация интерфейса). В языке динамической типизации вы можете сделать это во время выполнения, и никто не будет заботиться.Большинство языков динамической типизации имеют механизмы, обеспечивающие общее поведение, которое будет перехватывать любое сообщение, передаваемое их интерфейсу. Например, Ruby
method_missing
и PHP,__call
если я хорошо помню. Это означает, что вы можете делать любые интересные вещи во время выполнения программы и принимать решение о типе на основе текущего состояния программы. Это дает инструменты для моделирования проблемы, которые намного более гибки, чем, скажем, в консервативном статическом языке программирования, таком как Java.источник