Я прочитал соответствующий вопрос. Существуют ли какие-либо шаблоны проектирования, которые не нужны в динамических языках, таких как Python? и запомнил эту цитату на Wikiquote.org
Замечательная вещь о динамической типизации - она позволяет вам выражать все, что можно вычислить. А системы типов - системы типов, как правило, разрешимы, и они ограничивают вас подмножеством. Люди, которые предпочитают статические системы типов, говорят: «Это хорошо, это достаточно хорошо; все интересные программы, которые вы хотите написать, будут работать как типы ». Но это смешно - если у вас есть система типов, вы даже не знаете, какие там интересные программы.
--- Радио-Эпизод 140 по программной инженерии: Newspeak и сменные типы с Gilad Bracha
Интересно, есть ли полезные шаблоны дизайна или стратегии, которые, используя формулировку цитаты, "не работают как типы"?
Ответы:
Первоклассные типы
Динамическая типизация означает, что у вас есть первоклассные типы: вы можете проверять, создавать и хранить типы во время выполнения, включая собственные типы языка. Это также означает, что значения являются типизированными, а не переменными .
Язык со статической типизацией может создавать код, который также опирается на динамические типы, такие как диспетчеризация методов, классы типов и т. Д., Но таким образом, который обычно невидим для среды выполнения. В лучшем случае, они дают вам способ выполнить самоанализ. Кроме того, вы можете имитировать типы как значения, но тогда у вас есть специальная система динамических типов.
Однако динамические системы типов редко имеют только первоклассные типы. Вы можете иметь первоклассные символы, первоклассные пакеты, первоклассные .... все. Это противоречит строгому разделению между языком компилятора и языком времени выполнения в статически типизированных языках. То, что может делать компилятор или интерпретатор, может делать и среда выполнения.
Теперь давайте согласимся, что вывод типов - это хорошо, и мне нравится проверять мой код перед его запуском. Тем не менее, мне также нравится иметь возможность создавать и компилировать код во время выполнения. И я тоже люблю делать предварительные вычисления во время компиляции. В динамически типизированном языке это делается на том же языке. В OCaml у вас есть система типов модуль / функтор, которая отличается от основной системы типов, которая отличается от языка препроцессора. В C ++ у вас есть язык шаблонов, который не имеет ничего общего с основным языком, который обычно не знает типов во время выполнения. И это хорошо на тех языках, потому что они не хотят предоставлять больше.
В конечном счете, это не меняет действительно то, какое программное обеспечение вы можете разрабатывать, но выразительность меняет то, как вы разрабатываете их, и трудно ли это или нет.
Узоры
Шаблоны, основанные на динамических типах, являются шаблонами, которые включают динамические среды: открытые классы, диспетчеризацию, базы данных объектов в памяти, сериализацию и т. Д. Работают простые вещи, такие как универсальные контейнеры, потому что вектор не забывает во время выполнения о типе объектов, которые он содержит. (нет необходимости в параметрических типах).
Я попытался представить множество способов оценки кода в Common Lisp, а также примеры возможного статического анализа (это SBCL). Пример песочницы компилирует крошечное подмножество кода на Лиспе, извлеченное из отдельного файла. Чтобы быть достаточно безопасным, я изменяю читаемую таблицу, разрешаю только подмножество стандартных символов и заключаю в тайм-аут.
Ничто из вышеперечисленного не является «невозможным» для других языков. Подход с использованием плагинов в Blender, в музыкальных программах или IDE для статически скомпилированных языков, которые выполняют перекомпиляцию на лету и т. Д. Вместо внешних инструментов динамические языки предпочитают инструменты, которые используют информацию, которая уже есть. Все известные абоненты FOO? все подклассы BAR? все методы, которые специализированы по классу ZOT? это усвоенные данные. Типы являются еще одним аспектом этого.
(см. также: CFFI )
источник
Краткий ответ: нет, потому что эквивалентность по Тьюрингу.
Длинный ответ: этот парень тролль. Хотя системы типов «ограничивают вас подмножеством», это правда, но вещи вне этого подмножества, по определению, не работают.
Все, что вы можете сделать на любом языке программирования с полным набором Тьюринга (это язык, предназначенный для программирования общего назначения, а также множество других, которые этого не делают; это довольно низкая планка, которую можно очистить, и есть несколько примеров превращения системы в систему Тьюринга). завершить непреднамеренно) вы можете сделать на любом другом языке программирования полного Тьюринга. Это называется «эквивалентностью по Тьюрингу», и это означает только то, что говорится. Важно отметить, что это не означает, что вы можете так же легко выполнить другую вещь на другом языке - некоторые утверждают, что в этом и заключается весь смысл создания нового языка программирования: чтобы дать вам лучший способ выполнения определенных действий. вещи, которые сосут существующие языки.
Например, динамическую систему типов можно эмулировать поверх статической системы типов ОО, просто объявив все переменные, параметры и возвращаемые значения в качестве базового
Object
типа, а затем используя отражение для доступа к конкретным данным внутри, поэтому, когда вы это поймете, вы видите, что буквально ничего нельзя сделать на динамическом языке, чего нельзя сделать на статическом языке. Но делать это таким образом было бы большим беспорядком, конечно.Парень из цитаты прав, что статические типы ограничивают то, что вы можете сделать, но это важная особенность, а не проблема. Линии на дороге ограничивают то, что вы можете делать в своей машине, но считаете ли вы их ограничительными или полезными? (Я знаю, что не хотел бы ездить по оживленной, сложной дороге, где ничто не говорит машинам, едущим в противоположном направлении, чтобы держаться на своей стороне и не подходить туда, куда я еду!) Установив правила, четко определяющие, что Принимая во внимание недопустимое поведение и предотвращая его, вы значительно уменьшаете вероятность неприятного сбоя.
Кроме того, он искажает другую сторону. Дело не в том, что «все интересные программы, которые вы хотите написать, будут работать как типы», а скорее «все интересные программы, которые вы хотите написать, будут нуждаться в типах». Когда вы выходите за пределы определенного уровня сложности, становится очень трудно поддерживать базу кода без системы типов, чтобы держать вас в курсе, по двум причинам.
Во-первых, потому что код без аннотаций типов трудно читать. Рассмотрим следующий Python:
Как вы ожидаете, как будут выглядеть данные, полученные системой на другом конце соединения? И если он получает что-то, что выглядит совершенно не так, как вы узнаете, что происходит?
Все зависит от структуры
value.someProperty
. Но как это выглядит? Хороший вопрос! Что звонитsendData()
? Что это проходит? Как выглядит эта переменная? Откуда это? Если это не локально, вы должны проследить всю историю,value
чтобы отследить, что происходит. Может быть, вы передаете что-то еще, что также имеетsomeProperty
свойство, но оно не делает то, что вы думаете, что делает?Теперь давайте посмотрим на это с помощью аннотаций типов, как вы могли бы видеть в языке Boo, который использует очень похожий синтаксис, но статически типизирован:
Если что-то идет не так, внезапно ваша работа по отладке стала на порядок проще: посмотрите определение
MyDataType
! Кроме того, вероятность получить плохое поведение, потому что вы передали какой-то несовместимый тип, который также имеет свойство с тем же именем, внезапно сводится к нулю, потому что система типов не позволит вам совершить эту ошибку.Вторая причина основана на первой: в большом и сложном проекте у вас, скорее всего, есть несколько участников. (А если нет, то вы создаете его самостоятельно в течение длительного времени, что, по сути, одно и то же. Попробуйте прочитать код, написанный 3 года назад, если не верите мне!) Это означает, что вы не знаете, что было проходя через голову человека, который написал почти любую конкретную часть кода в то время, когда они его написали, потому что вас там не было или вы не помните, был ли это ваш собственный код давным-давно. Наличие объявлений типов действительно помогает вам понять, каково было намерение кода!
Такие люди, как парень в цитате, часто неверно характеризуют преимущества статической типизации как «помощь компилятору» или «все в отношении эффективности» в мире, где почти неограниченные аппаратные ресурсы делают это все менее и менее актуальным с каждым годом. Но, как я показал, хотя эти преимущества, безусловно, существуют, основное преимущество заключается в человеческих факторах, в частности, в удобочитаемости кода и его поддержке. (Тем не менее, дополнительная эффективность, безусловно, хороший бонус!)
источник
Я собираюсь обойти часть «шаблона», потому что я думаю, что она переходит в определение того, что является или не является шаблоном, и я давно потерял интерес к этим дебатам. Я скажу, что есть вещи, которые вы можете делать на одних языках, которые вы не можете делать на других. Позвольте мне прояснить, я не говорю, что есть проблемы, которые вы можете решить на одном языке, которые вы не можете решить на другом. Мейсон уже указал на полноту Тьюринга.
Например, я написал класс на python, который оборачивает элемент XML DOM и превращает его в объект первого класса. То есть вы можете написать код:
и у вас есть содержимое этого пути из проанализированного XML-объекта. вроде аккуратно и аккуратно, ИМО. И если нет головного узла, он просто возвращает фиктивные объекты, которые не содержат ничего, кроме фиктивных объектов (черепахи все время вниз). Нет реального способа сделать это, скажем, в Java. Вы должны были бы заранее скомпилировать класс, основанный на некотором знании структуры XML. Не говоря уже о том, хорошая ли это идея, такого рода вещи действительно меняют способ решения проблем на динамическом языке. Я не говорю, что это меняется таким образом, что всегда всегда лучше, однако. Есть определенные издержки для динамических подходов, и ответ Мейсона дает достойный обзор. Являются ли они хорошим выбором, зависит от многих факторов.
Кстати, вы можете сделать это в Java, потому что вы можете создать интерпретатор Python в Java . Тот факт, что решение конкретной проблемы на данном языке может означать создание переводчика или чего-то похожего на него, часто упускается из виду, когда люди говорят о полноте по Тьюрингу.
источник
IDynamicMetaObjectProvider
, а в Boo - просто. ( Вот реализация в менее чем 100 строк, включенная как часть стандартного дерева исходников на GitHub, потому что это так просто!)"IDynamicMetaObjectProvider"
? Это связано сdynamic
ключевым словом C # ? ... который фактически просто привязывает динамическую типизацию к C #? Не уверен, что ваш аргумент верен, если я прав.dynamic
достигается в C #. «Отражение и поиск в словаре» происходят во время выполнения, а не во время компиляции. Я действительно не уверен, как вы можете сделать так, чтобы это не добавляло динамической типизации к языку. Я хочу сказать, что последний абзац Джимми охватывает это.Цитата верна, но тоже неискренна. Давайте разберемся, чтобы понять почему:
Ну, не совсем. Язык с динамической типизацией позволяет выразить все , как долго , как это Тьюринг , который большинство из них. Сама система типов не позволяет вам выражать все. Давайте дадим ему преимущество сомнения здесь.
Это правда, но обратите внимание, что сейчас мы твердо говорим о том, что позволяет система типов , а не о том, что позволяет язык , использующий систему типов. Хотя можно использовать систему типов для вычисления материала во время компиляции, это, как правило, не является полным по Тьюрингу (так как система типов обычно разрешима), но почти любой статически типизированный язык также завершается по Тьюрингу во время выполнения (зависимо типизированные языки нет, но я не верю, что мы говорим о них здесь).
Проблема в том, что языки динамического типа имеют статический тип. Иногда все является строкой, и чаще всего существует какой-либо теговый союз, где каждая вещь является либо сумкой свойств, либо значением типа int или double. Беда в том, что статические языки тоже могут это делать, исторически это было немного сложнее, но современные статически типизированные языки делают это почти так же просто, как и использование языков динамического типа, так как же может быть разница в что программист может увидеть как интересную программу? Статические языки имеют точно такие же помеченные объединения, как и другие типы.
Чтобы ответить на вопрос в заголовке: Нет, нет шаблонов проектирования, которые не могут быть реализованы на языке со статической типизацией, потому что вы всегда можете реализовать достаточно динамической системы, чтобы получить их. Могут быть шаблоны, которые вы получаете за «бесплатно» на динамическом языке; это может или не может стоить мириться с недостатками этих языков для YMMV .
источник
Конечно, есть вещи, которые вы можете делать только на динамически типизированных языках. Но они не обязательно будут хорошим дизайном.
Вы можете назначить сначала целое число 5, а затем строку
'five'
илиCat
объект той же переменной. Но вы только усложняете для читателя вашего кода понимание того, что происходит, какова цель каждой переменной.Вы можете добавить новый метод в библиотечный класс Ruby и получить доступ к его закрытым полям. Могут быть случаи, когда такой взлом может быть полезен, но это будет нарушением инкапсуляции. (Я не против добавления методов, полагающихся только на общедоступный интерфейс, но это то, что статически типизированные методы расширения C # не могут сделать.)
Вы можете добавить новое поле к объекту чужого класса, чтобы передать ему дополнительные данные. Но лучше создать новую структуру или расширить исходный тип.
Как правило, чем более организованно вы хотите, чтобы ваш код оставался в памяти, тем меньше у вас преимуществ от возможности динамически изменять определения типов или присваивать значения разных типов одной и той же переменной. Но тогда ваш код ничем не отличается от того, что вы могли бы достичь в статически типизированном языке.
В чем динамические языки хороши - это синтаксический сахар. Например, при чтении десериализованного объекта JSON вы можете ссылаться на вложенное значение просто как
obj.data.article[0].content
- намного точнее, чем сказатьobj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content")
.Разработчики Ruby особенно могли бы подробно рассказать о магии, которая может быть достигнута путем реализации
method_missing
, которая представляет собой метод, позволяющий обрабатывать попытки вызова необъявленных методов. Например, ActiveRecord ORM использует его, чтобы вы могли сделать вызов,User.find_by_email('joe@example.com')
даже не объявляяfind_by_email
метод. Конечно, ничего такого, чего нельзя достичь, какUserRepository.FindBy("email", "joe@example.com")
в статически типизированном языке, но нельзя отрицать его аккуратность.источник
Шаблон Dynamic Proxy - это ярлык для реализации прокси-объектов без использования одного класса для каждого типа, который вам нужен для прокси.
Используя это,
Proxy(someObject)
создает новый объект, который ведет себя так же, какsomeObject
. Очевидно, вы также захотите как-то добавить дополнительную функциональность, но это полезная основа для начала. В полном статическом языке вам нужно было бы либо написать один класс Proxy для каждого типа, который вы хотите использовать для прокси, либо использовать динамическую генерацию кода (которая, по общему признанию, включена в стандартную библиотеку многих статических языков, в основном потому, что их разработчики знают о проблемы не в состоянии сделать это причина).Другим вариантом использования динамических языков является так называемое «исправление обезьян». Во многих отношениях это скорее анти-паттерн, чем паттерн, но его можно использовать полезными способами, если делать это осторожно. И хотя нет теоретической причины, по которой исправление обезьян не может быть реализовано на статическом языке, я никогда не видел ни одного, у которого оно действительно есть.
источник
Да , есть много шаблонов и техник, которые возможны только в динамически типизированном языке.
Исправление обезьян - это метод, при котором свойства или методы добавляются к объектам или классам во время выполнения. Этот метод невозможен в статически типизированном языке, поскольку это означает, что типы и операции не могут быть проверены во время компиляции. Или, другими словами, если язык поддерживает патчирование обезьян, это по определению динамический язык.
Можно доказать, что если язык поддерживает исправление обезьян (или аналогичные методы для изменения типов во время выполнения), он не может быть статически проверен на тип. Таким образом, это не просто ограничение в существующих в настоящее время языках, это фундаментальное ограничение статической типизации.
Таким образом, цитата определенно верна - в динамическом языке возможно больше вещей, чем в языке со статической типизацией. С другой стороны, определенные виды анализа возможны только в статически типизированном языке. Например, вы всегда знаете, какие операции разрешены для данного типа, что позволяет обнаруживать недопустимые операции при типе компиляции. Такая проверка невозможна на динамическом языке, когда операции могут быть добавлены или удалены во время выполнения.
Вот почему не существует очевидного «лучшего» в конфликте между статическими и динамическими языками. Статические языки отдают определенную мощность во время выполнения в обмен на другую мощность во время компиляции, которая, по их мнению, уменьшает количество ошибок и облегчает разработку. Некоторые считают, что компромисс стоит того, другие нет.
Другие ответы утверждают, что эквивалентность по Тьюрингу означает, что все, что возможно на одном языке, возможно на всех языках. Но это не следует. Чтобы поддерживать что-то вроде «мартышки-патчинга» в статическом языке, вам в основном необходимо реализовать динамический дополнительный язык внутри статического языка. Это, конечно, возможно, но я бы сказал, что вы программируете на встроенном динамическом языке, так как вы также теряете статическую проверку типов, существующую в языке хоста.
C #, начиная с версии 4, поддерживает динамически типизированные объекты. Очевидно, что дизайнеры языка видят выгоду в том, что оба вида печати доступны. Но это также показывает, что вы не можете иметь свой пирог и есть я тоже: когда вы используете динамические объекты в C #, вы получаете возможность делать что-то вроде исправления обезьян, но вы также теряете статическую проверку типов для взаимодействия с этими объектами.
источник
Да и нет.
Есть ситуация, в которой программист знает тип переменной с большей точностью, чем компилятор. Компилятор может знать, что что-то является объектом, но программист будет знать (из-за инвариантов программы), что это на самом деле строка.
Позвольте мне показать несколько примеров этого:
Я знаю, что
someMap.get(T.class)
вернетFunction<T, String>
, из-за того, как я построил SomeMap. Но Java уверена только в том, что у меня есть функция.Другой пример:
Я знаю, что data.properties.rowCount будет действительной ссылкой и целым числом, потому что я проверил данные по схеме. Если бы это поле отсутствовало, возникло бы исключение. Но компилятор может знать только, что он генерирует исключение или возвращает какой-то типовой JSONValue.
Другой пример:
«II6s» определяет способ, которым данные кодируют три переменные. Поскольку я указал формат, я знаю, какие типы будут возвращены. Компилятор будет знать только, что он возвращает кортеж.
Объединяющей темой всех этих примеров является то, что программист знает тип, но система типов уровня Java не сможет отразить это. Компилятор не будет знать типы, и, следовательно, статически типизированный язык не позволит мне их вызывать, в то время как динамически типизированный язык узнает.
Вот к чему приводит оригинальная цитата:
При использовании динамической типизации я могу использовать самый производный тип, о котором я знаю, а не просто самый производный тип, который знает моя система типов языка. Во всех описанных выше случаях у меня есть код, который семантически корректен, но будет отклонен статической системой типизации.
Однако вернемся к вашему вопросу:
Любой из приведенных выше примеров, а также любой пример динамической типизации можно сделать допустимым при статической типизации путем добавления соответствующих приведений. Если вы знаете тип, которого нет у вашего компилятора, просто скажите компилятору, приведя значение. Итак, на каком-то уровне вы не получите никаких дополнительных шаблонов с помощью динамической типизации. Вам просто может потребоваться привести больше, чтобы получить статически типизированный код.
Преимущество динамической типизации заключается в том, что вы можете просто использовать эти шаблоны, не беспокоясь о том, что сложно убедить вашу систему типов в их достоверности. Он не меняет доступные шаблоны, он просто делает их проще в реализации, потому что вам не нужно выяснять, как заставить систему типов распознавать шаблоны или добавлять приведения, чтобы подорвать систему типов.
источник
data = parseJSON<SomeSchema>(someJson); print(data.properties.rowCount);
если у человека нет класса для десериализации, мы можем вернуться кdata = parseJSON(someJson); print(data["properties.rowCount"]);
- который все еще набран и выражает то же самое намерение.data.properties
это объект, и я знал, чтоdata.properties.rowCount
это целое число, и я мог просто написать код, который их использовал. Ваше предложениеdata["properties.rowCount"]
не обеспечивает то же самое.Вот несколько примеров из Objective-C (динамически типизированных), которые невозможны в C ++ (статически типизированные):
Помещение объектов нескольких разных классов в один контейнер.
Конечно, для этого требуется проверка типа во время выполнения для последующей интерпретации содержимого контейнера, и большинство друзей статической типизации возразят, что вам не следует делать это в первую очередь. Но я обнаружил, что помимо религиозных дебатов это может пригодиться.
Расширение класса без создания подклассов.
В Objective-C вы можете определить новые функции-члены для существующих классов, в том числе такие, как язык
NSString
. Например, вы можете добавить методstripPrefixIfPresent:
, чтобы вы могли сказать[@"foo/bar/baz" stripPrefixIfPresent:@"foo/"]
(обратите внимание на использованиеNSSring
литералов@""
).Использование объектно-ориентированных обратных вызовов.
В статически типизированных языках, таких как Java и C ++, вы должны приложить значительные усилия, чтобы позволить библиотеке вызывать произвольный член предоставленного пользователем объекта. В Java обходной путь - это пара интерфейс / адаптер плюс анонимный класс, в C ++ обходной путь обычно основан на шаблонах, что подразумевает, что код библиотеки должен быть представлен пользовательскому коду. В Objective-C вы просто передаете ссылку на объект плюс селектор метода в библиотеку, и библиотека может просто и напрямую вызывать обратный вызов.
источник
void*
только @JiriDanek - это не динамическая типизация, это недостаток типизации. Но да, dynamic_cast, виртуальные таблицы и т. Д. Делают C ++ не просто статически типизированным. Это плохо?void*
конкретный тип объекта. Первый выдает ошибку во время выполнения, если вы все испортили, позднее приводит к неопределенному поведению.