Страницы

Поиск по вопросам

понедельник, 8 октября 2018 г.

Динамические свойства языка программирования как преимущество

Наверстываю упущенную теорию по парадигмам языков программирования.
В обучающих материалах по ObjC часто встречается восхищение динамичностью языка (динамическое связывание, динамическая типизация...) с указанием на то что многое можно делать в рантайме плюс описание соответствующих трюков. Читая документ со страницы 23 я понимаю что это есть свойство языка, но не преимущество. Предполагаю, что сравнивая со статически типизированными языками все те же трюки выполняются в них с тем же успехом, но не в runtime a в compile-time. Раздел статьи Преимущества (равно как и другие статьи в сети) свет на вопрос для меня не пролил и не дал понять в чем именно преимущество а не просто отличие от парадигмы статической типизации. Прошу просветить и указать в чем именно преимущество динамической типизации над статической.
UPDATE: Если будут получены объемные ответы на вопрос о природе явления в целом, то с целью оставить тред потомкам я добавлю метки всех языков соответствующих парадигме и уберу акцент на objc из топика.
UPDATE 2: Получены прекрасные всеобъемлющие ответы, в частности от @VladD. C целью аккумулирования топиком общеобразовательной ценности хотелось бы читать мнение и других участников форума. Заранее благодарен за передачу опыта и знаний!


Ответ

Если уж зашла речь о «динамичности языка» вообще, должен отметить, что отдельное понятие «динамического языка» лишено смысла. В основном, когда говорят о «динамическом языке», имеют в виду «язык с динамической системой типов», хотя существуют и другие «динамические» черты языка (первое, что приходит в голову — «динамическая область видимости»). Итак, что такое «динамическая система типов»? Определения в источниках наподобие Википедии говорят, что разница в том, присваивается ли тип переменной при объявлении (и остаётся ли таким навсегда) либо при присваивании (и меняется с каждым следующим присваиванием). Это, однако, слишком грубое описание существующего положения вещей. Куда честнее и точнее было бы говорить о богатстве системы типов, и о том, насколько полно она отображает семантику предметной области. Крайний пример — языки типа большинства ассемблеров, в которых существует лишь один тип — числовой. Это соответствует примерам «максимально динамической» системы типов: переменная может содержать число, указатель, структуру из двух байт и т. д. Примера противоположной крайности — языка, в котором каждый элемент программной семантики описывается отдельным типом — наверное не существует, но старые диалекты Паскаля довольно близки к этому. Полностью последовательным строго типизированным языком был бы язык, в котором, например, была бы невозможна ошибка при взятии квадратного корня из отрицательного числа: в языке был бы определён тип «неотрицательное действительное число», и этот тип был бы типом аргумента корня. (То же для например арксинуса.) На деле же все языки стоят где-то посередине между этими двумя крайностями. Языки, которые считаются «динамически типизированными» или «слабо типизированными» (в зависимости от того, одобряет или осуждает программист это явление), имеют меньшее количество типов, и операции между значениями с разной семантикой либо завершаются с ошибкой, либо приводят к бессмысленному результату. В качестве примера рассмотрим javascript. Его переменные, по существу, имеют лишь один тип — переменная :-) Вы можете вызвать любую операцию на переменной, вне зависимости от фактического содержимого этой переменной. Для некоторых операций (например, вызов функции) неправильное содержимое переменной вызовет исключение, для некоторых же (например, сложение) операция будет выполнена с результатом, неожиданным для программиста, который не во всех деталях проштудировал спецификацию языка. Языки наподобие C++ предоставляют более богатую систему типов: вы, например, не сможете «вызвать функцию» по переменной типа int. Таким образом, у операций в C++ гораздо меньше возможностей завершиться аварийно из-за несоответствия типов. Тем не менее, язык предоставляет вам возможность нарушить защиту: оператор приведения типов. Вы можете привести тип строки к типу числа, и попытаться выполнить числовую операцию с полученным значением. Согласно стандарту языка, вы получите неопределённое поведение, то есть, что угодно имеет право произойти. Кроме того, определённые черты слабо типизированных языков есть и в C++: операция сложения между различными арифметическими типами вызывает неявную конверсию операндов к наиболее общему типу. Язык наподобие C# мог бы казаться ещё более статически типизированным (в нём нету аналога reinterpret_cast, и тип проверяется всегда), но зато в нём есть ключевое слово dynamic, близкое по идеологии к eval. Из особенностей динамических языков, перечисленных в Википедии, C# обладает в той или иной степени всеми, за исключением, пожалуй, макросов. Где-то посередине по богатству системы типов стоит SQL: в нём есть набор примитивных типов, но нет возможности расширить систему типов, применимо к нуждам пользователя. Итак, под динамической системой типов обычно понимается система типов, в которой меньше базовых типов, и/или некоторые операции могут применяться к разным типам с возможностью получения ошибки времени выполнения либо малоосмысленного результата. Большинство языков обладают этим свойством в той или иной мере. Литература для дальнейшего чтения: [1], [2], [3], [4] Преимущества «более динамических» языков: Лучшая выразительность в некоторых сценариях, например, там где программист хочет сказать «я уверен, что этот объект умеет выполнить эту операцию» или «объекты, не умеющие делать это, игнорируются». Для сильнее типизированных языков такая семантика достигается использованием приведения типов или другой разновидностью проверки времени выполнения. Например, в Objective C, насколько я знаю, можно отправить произвольное сообщение произвольному объекту (разновидность duck typing); в C# для этого нужно, чтобы объект поддерживал соответствующий интерфейс, или использование dynamic, то есть откладывание проверки до момента выполнения. Неявная, «плавающая» семантика объектов по сравнению с явно заданными, продуманными и жёстко прописанными типами позволяет легко менять смысл классов и операций при разработке. (Не могу придумать пример для Objective C.) Это увеличивает скорость прототипирования, хотя, возможно, и негативно скажется на качестве кода при длительной разработке проекта. Отсюда распространённое мнение о том, что «динамические» языки лучше подходят для построения proof of concept, а статические — для имплементации. Более удобное общение с популярными нетипизированными или слаботипизированными структурами, такими как XML- и JSON-документы. Ещё один часто забываемый пример динамического свойства языков: виртуальные функции. Преимуществом более динамических языков является более простая диспетчеризация вызовов функций: если у меня определены f(Base, Base), f(Base, Derived), f(Derived, Base) f(Derived, Derived), язык с развитой динамической типизацией сможет автоматически выбрать нужную перегрузку при вызове метода, в то время как другие языки вынуждены пользоваться паттерном наподобие Visitor. Частным случаем диспетчеризации по одному, нулевому аргументу является вызов виртуальной функции. Некоторые языки умеют управлять кодом во время выполнения. Например, в Javascript'е можно переопределить любой метод через прототип, в Objective C можно подменить класс, используя категории (если я не ошибаюсь), в Питоне можно модифицировать поведение методов при помощи аннотаций. Достижение подобных эффектов в не приспособленных для этого языках сопряжено со значительным объёмом кода и нередко требует от пользователей неестественного, неудобного или даже опасного синтаксиса. (Например, оборачивать каждое объявление поля в макрос.) Суммируя: для понятий, встроенных в систему типов, динамически типизированные языки проигрывают статически типизированным в выразительной силе (пример: в статическом языке легко запретить складывать длину с весом, а в динамическом — нет). Для понятий, не описываемых системой типов, динамические языки выразительнее (пример всем объектам с полем возраст увеличить этот самый возраст на 1: в статических языках требует интерфейса, одинакового имени свойства и одинакового типа свойства). Многие преимущества, которые традиционно считаются преимуществами динамических языков, на самом деле лишь следствия хорошо организованной среды выполнения (рантайма), и в частности не требуют интерпретации или виртуальной машины. Например, интроспекция/рефлексия есть не вопрос динамичности, а лишь вопрос доступности метаданных во время выполнения, и вполне статичная с точки зрения системы типов Java её реализует. Замыкания, которые в Javascript'е есть чисто динамическая особенность, реализуются статически с полным контролем типизации в Objective C (под именем блоков) и C#. Английская Википедия считает, что динамические черты ... can be emulated in nearly any language of sufficient complexity, but dynamic languages provide direct tools to make use of them. (могут быть эмулированы в любом достаточно сложном языке, но динамические языки предоставляют прямой метод ими воспользоваться) — но я позволю себе частично не согласиться. То, как динамические свойства языка реализованы, может быть сделано при подходящей реализации и в любом другом языке, так что слово «эмуляция» здесь неприменимо: если статический язык «эмулирует» рефлексию при помощи метаданных, что же делает динамический? Итак, мы видим, что граница между статическими и динамическими языками довольно сильно размыта.

Комментариев нет:

Отправить комментарий