Конец эпохи динамических языков

http://elbenshira.com/blog/the-end-of-dynamic-languages/
  • Перевод
  • Tutorial
Несколько последних месяцев я программирую преимущественно на Scala (по работе) и на Haskell (для души). На этой неделе я, правда, ещё немного пописал на Ruby (по работе) и Clojure (для души).

Ruby вывел меня из равновесия почти сразу. Нет, ну ещё в плане «добавить небольшую фичу к уже имеющемуся коду» писать на нём можно. Вы просто добавляете юнит тест, запускаете его на старом коде, делаете правку, запускаете тест снова — вуаля, готово, забирайте. Но замахиваться на что-то большее становится уже слишком сложно.

Но вот что касается моего новенького, с иголочки, проекта-любимца на Clojure… О, Clojure! Глоток свежего воздуха! Благодатная земля хорошо скомпонованных функций, иммутабельных структур данных и всего такого. Как прекрасен твой синтаксис и как мудра твоя чувствительность! Вся твоя суть в функциях, принимающих мэпы и возвращающих мэпы. И твой SQL-генератор, и слой доступа к БД, и HTML-парсер, и URL-роутер являют собой одну и ту же завораживающую картину мэпов, гоняемых туда-сюда тактами процессора, прекрасную с своём ритме хорошо собранных швейцарских часов.

Вернуться к Clojure после долгого времени это всё равно, что почувствовать себя дома. Это просто окрыляет программиста. Но почему-то в этот раз я ощутил и ещё одно, неожиданное для себя чувство: неопределённость.

Мой диалог с Clojure выглядел как-то так:

— О, свет мой, Clojure! Спасибо тебе за эту восхитительную иммутабельную структуру данных с мэпом для запроса. Могу ли я спросить, что там внутри?
— Разве это не очевидно? Там HTTP-запрос.
— Да-да, конечно. Но в каком же именно виде? Что там за ключи и что за значения?
— Разве это не очевидно? Там HTTP-запрос.
— Да-да, конечно. Я почитаю исходники и документацию в поисках ответа.
— Да, почитай и разберись.
— Я почитал. И что же такое переменные attr и f? А когда я вызываю функцию wrap-params — какие ключи добавляются в мэп?
— Разве это не очевидно?
— Забудь. Я просто добавлю вот сюда и сюда отладочный вывод.

Всё это бьёт по продуктивности. Каждый вызов библиотечной функции, если вы только его не выучили наизусть, требует рыться в исходниках в попытках понять, как же его использовать. Каждый мэп, полученный после вызова, требует отладочного вывода чтобы понять, что с ним делать.

Да, Clojure — мощная вещь. Но мощь эта неуправляема, она не имеет вектора и без кого-то, способного дать совет, она может лишь разрушать. И я сейчас не о философских понятиях — только о коде. Кто из нас не страдал от метапрограммирования в Ruby или мэпов в Clojure? Мы сами себе и жертвы и виновники наших страданий.

Вот вам пример: предметно-ориентированные языки (DSL) — это способ решения проблем, или ещё один способ их создания? Давайте поговорим о деструктивной мощи DSL в Clojure. Программисты на Clojure любят DSL-и, структура языка предрасполагает к их использованию. Но я считаю, что что-то здесь не так. Давайте, например, представим такой-себе генератор HTML. Вот вы вместо

<span class="foo">bar</span>


пишете:

[:span {:class "foo"} "bar"]


Знаете, какой DSL лучше всего описывает HTML? Я открою вам секрет: это HTML. Так зачем же вы, скажите пожалуйста, придумываете вместо него свой собственный DSL, который точно ничем не лучше (а скорее всего хуже) и уж наверняка игнорирует все те десятилетия опыта и развития, что были у HTML со всеми его фичами, инструментами и опытом дизайнеров?

Посмотрите как сделаны enlive и yesql и вы поймёте, о чём я говорю.

Но что ещё хуже с DSL, так это то, что я не уверен, верно ли я использую ваш DSL до того самого момента, пока я не получу ошибку на рантайме. Давайте посмотрим на bidi, миленькую библиотеку для URL-роутинга. Её приятно использовать, но есть одно «но». Скажем, мы хотим прописать маршрут для запроса GET /notes. В bidi мы определяем маршруты вот так:

(def my-routes
  ["/notes" :index-handler])


Мы можем протестировать этот обработчик:

(bidi/match-route my-routes "/notes")
;; {:handler :index-handler}
;; Success!

Выглядит просто. Но что, если мне нужно добавить ещё пяток маршрутов:

GET /
GET /notes
GET /notes/:id
POST /notes
POST /notes/:id

Какая ерунда! Всего-то несколько Ctrl+F по документации, внимательное чтение исходников, пару догадок, десяток экспериментов, невероятное количество исключений, выброшенных прямо мне в лицо и вот я чудом дохожу до требуемого результата:

(def my-routes
  ["" {"/" :home-page-handler
       "/notes"
         {:get  {"" :index-handler}
          :post {"" :create-handler}
          ["/" :id] {:get  {"" :show-handler}
                     :post {"" :update-handler}}}}])

Я думаю, во всём этом есть некий неуловимый паттерн, но вот вы видите его с первого взгляда? Вы запомните его раз и навсегда, или в следующий раз снова будете идти по тому же устеленному граблями пути творческого поиска?

Проблема неопределенности
Не поймите меня превратно: при использовании типизированных языков тоже возникают негативные ощущения. Замешательство, разочарование, отчаяние. Но неопределённость хуже их всех. Вы можете бороться со всеми остальными методом «надо посидеть и разобраться раз и навсегда». Но как разобраться с неопределённостью? Только с помощью определённости. А что, если сам язык не предоставляет никаких средств, чтобы нащупать эту твёрдую почву под ногами?

Давайте посмотрим на несколько текущих попыток решить эту проблему

Частичное введение типов (Gradual typing)
Есть фантастически интересные попытки скрестить бульдога с носорогом и прикрутить к динамическим языкам элементы статической системы типов: Typed Racket, Typed Clojure, TypeScript, Typed Lua. Даже для Python есть "type hints"

Всё это хорошие вещи, их давно не хватало. Они показывают наличие проблемы неопределённости, о которой я здесь говорю, и каких-то попытках её решить.

К сожалению, у меня есть подозрение, что частичного введения типов будет не достаточно. Во-первых, нужно затратить прилично усилий, чтобы описать типы во всех уже существующих библиотеках. Может ли сообщество Clojure быть достаточно дисциплинированным, чтобы добавить описание типов для всех своих библиотек? Сколько из них уже описаны? Я не вижу активной работы в этом направлении.

Мы можем на словах бороться за культуру ответственности и заботы о коде, но знаете, рано или поздно доходит до того, что вы пишете какой-нибудь комментарий по принципу «ай, сойдёт», а не стараетесь сделать его максимально полезным и эффективным. Почему же так происходит?

Фундаментальная проблема в том, что язык программирования — это не только код. В сообществе языка также воплощаются некоторые идеи его создателей, подходы, школы, философия. И всё это очень тяжело потом изменить.

Этого не достаточно
А что на счёт юнит тестов, тестов стиля кода, контрактов, парного программирования, ревью кода? Какое множество полезных инструментов! Наверняка уж с ними-то можно и на динамических языках писать!

Нет, нельзя. Юнит тесты полезны для тестирования некоторой известной функциональности, а не для определения того, насколько что-то в результатах соответствует вашим явным или неявным ожиданиям. Контракты могут описывать намерения что-то реализовать, а не то, как оно на самом деле реализовано. А парное программирование и ревью кода имеют всем известное слабое звено — человеческий фактор.

Тесты стиля кода для динамических языков бессмысленно ущербны в том плане, что заботятся они о внешнем виде кода, а не о его качестве. Да, проблему с лишним пробелом они найдут, но в пробеле ли действительно проблема?

В то же время использование hlint в Haskell (поскольку это типизированный язык) даёт весьма ощутимый результат. Инструмент знает о вашей программе намного больше, чем просто синтаксис её кода. Он способен находить такие структурные проблемы, как:

  • Есть две эквивалентные анонимные функции, которые можно заменить одной и вызывать из двух мест
  • Вы написали код, который уже содержится в какой-нибудь доступной в проекте библиотеке
  • Вы не перебрали все возможные варианты в case-выражении


Создаётся впечатление, что мы тут говорим об искусственном интеллекте. Ок, а что если мы начнём с инструментов, которые помогут нам предотвращать некоторые рантайм-ошибки и помогут писать качественный код? Хотели бы вы иметь такие штуки, правда?

Короткая история
Так, хватит теории, сделаем небольшую паузу, где я расскажу как мы используем наши языки программирования для искоренения некоторого класса ошибок из нашей кодовой базы.

На прошлой неделе я работал с одним проектом на Scala и по ходу дела заметил, что в нём очень легко сделать ошибку, приводящую к записи неверных значений в поисковый кластер Solr. Например, Solr с лёгкостью соглашается записывать значение null в булевое поле. Я потратил неделю на рефакторинг этого хаоса, разбив этот огромный подверженный багам монолитный кусок на маленькие симпатичные модули, крутящиеся вокруг иммутабельных структур данных, как в Clojure. Вначале я был очень доволен собой и кодом. Его было легко читать и легко тестировать. Всё было прекрасно, пока я не обнаружил, что пишу те же самые баги, что и раньше. Один из них даже сломал наш порно-фильтр. Быстрый вопрос: что вы будете делать, когда функция isItPorn() вернёт null?

Тогда я и мой коллега Адам поставили цель: превратить все эти потенциальные ошибки на рантайме в ошибки на этапе компиляции. Мы хотели получить пусть большое и страшное, но генерируемое компилятором сообщение об ошибке, когда мы попытаемся записать null в булевое поле.

У нас заняло 2 дня чтобы выяснить как это всё можно завязать на систему типов Scala. Но в конце концов мы сделали это. Наша система типов стала проще, мы смогли конвертировать все имеющиеся раньше потенциальные ошибки рантайма в ошибки при компиляции. Адам отрефакторил всю нашу кодовую базу для использования новой системы типов всего за пару часов.

Сегодня я обновил один из моих проектов с использованием данной библиотеки. Получил ошибку от компилятора. Это действительно был баг, я никогда не видел его раньше. Исправлено!

Подобная мощь никогда не будет доступна в языках вроде Ruby и, наверное, Clojure тоже. Но она существует. И вы тоже можете использовать её.

На всякий случай уточню: вы и ваша команда не станете писать идеальный код прямо в тот же день, когда перейдёте с Ruby на что-то другое. На самом деле вас ждёт приличный этап падения производительности и, возможно, крепких выражений, звучащих в офисе. Но это боль растущего профессионализма — она неизбежна при изучении чего-то нового. А потом придёт уверенность и мастерство.

Конец эпохи
Вот моя ставка: эпоха динамических языков заканчивается. Больше не будет новых успешных чисто динамических языков. Но они нас многому научили. Теперь мы знаем, что код библиотеки должен поддаваться расширению другими программистами (польза миксинов и мета-программирования), что мы хотим понимать и контролировать структуры данных, что мы презираем многословность. Ах, и ещё мы научились получать удовольствие от использования языков программирования.

Но время двигаться дальше. Мы вскоре увидим сияние восхода нового поколения языков программирования — приятных в использовании, как Clojure, но типизированных. Вместе с ними придут и новые, невиданные доселе инструменты, которые не сможет проигнорировать даже самый отъявленный аскет.

Чтобы всё это произошло, нам необходимо дать нашим инструментам ту информацию, которая им необходима, чтобы нам помочь. Даже хороший психолог не может помочь абсолютно молчаливому пациенту. Мы начнём с предоставления информации о типах, ограничив тем самым область неопределённости. Новые языки программирования, такие как Elm и Crystal находятся на верном пути, равно как и уже существующие Haskell и Scala. Нам нужно больше языков подобного типа.

Мы думаем, что видели в жизни уже всё, что нет в программировании новых идей, которые были бы способны удивить. Хуже того — мы отказываемся даже изучить что-то, что не согласуется с нашим предыдущим опытом. Ну, знаете, вот это «Боже, посмотрите только на синтаксис!». Но вспомните, как тяжело вам было тогда, когда вы учили свой первый язык программирования — там что, было понятнее? Но ведь оно того стоило, правда? Не нужно бояться проделать это захватывающее путешествие ещё раз.

Теолог Герхард Вос однажды описал человеческую жизнь как «уже, но всё ещё нет». С его точки зрения верующим людям судьбой предопределено счастье в загробной жизни, но вот пока ещё им приходится мириться с мирскими бедами и тягостями. Эта метафора хорошо описывает и то, где сейчас находимся мы, программисты. Да, у нас пока нет идеальных языков программирования и действительно умных инструментов, но с моей точки зрения блистательное будущее этой сферы человеческой деятельности уже предопределено. Многие из сегодняшних разработок уже идут по правильному пути, а чему-то ещё только предстоит появиться. Нам ещё предстоит длинная дорога, но мы определённо сможем её пройти.
Метки:
Инфопульс Украина 186,14
Creating Value, Delivering Excellence
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 247
  • +6
    Согласен полностью. Динамические языки это классно когда маленькие вещи пишешь а потом просто АД. Согласен что не просто так много новых языков они статические потому что есть спрос и понимание. Я нашол себе GO для этих вещей. Стаческий, быстры и код одинако похож у разных людей.
    • –3
      Вполне логично. Динамические языки это просто подмножество статических. Во многих статических языках вполне могут существовать типы any (dynamic) для «истинно динамического типа данных» и variant для суммы типов из фиксированного набора (алгебраический тип данных).
      Динамический язык — это по сути статический, в котором из типов есть только «any».
      • +9
        по-мойму как раз таки наоборот. Динамические языки имеют более общей смысл, как в математике натуральные числа являются подмножеством целых.
        • +1
          Мы же говорим не о математике, а о программировании.
          Но даже если и сравнить с числами — никакого более общего смысла в динамических языках нет. В статических языках появляются дополнительная возможность — типизация на этапе компиляции, которой нет в динамических. При этом в современных статических языках есть все возможности из динамических. То есть этой типизацией можно пользоваться, а можно и не пользоваться. То есть динамические имеют меньше возможностей по сравнению с современными статическими (хотя их правильнее наверное называть комбинированными, т.к. они сочетают и динамику и статику), и поэтому яляются подмножеством. Чистая теория множеств:)
          • –3
            А что при использовании динамического языка мешает:
            а) указывать тип переменной каким-то образом (доки, спец. средства, вроде type hints для питона и т.п.)
            б) сделать анализатор кода, который будет пытаться выводить тип переменных на основе подсказок о типе
            в) запускать анализатор для всего проекта перед запуском самого проекта (и назвать эту операцию «компиляция»).
            г) при ошибке анализатора о несоответствии типов запретить запускать программу

            Понятно, что сделать анализатор кода для динамического языка намного труднее. Но и статический анализ для статических языков неидеален.
            • +10
              что при использовании динамического языка мешает

              Ничего, кроме того что вы предлагаете самому написать новый статический язык с новыми правилами… По сути, вы предлагаете самому написать аналог TypeScript для JavaScript, это, конечно, возможно, но смысл?
              • 0
                Но в моём случае это будет не другой язык. а тот же самый. И этап В в моём случае можно отключить, при этом программа будет работать.

                Вообще, NeoCode писал, что проверять соответствие типов ещё до запуска можно только в статически типизированных языках. Однако, это не так. Просто в статических языках эта проверка входит в спецификацию языка, а для динамического — нет.

                Уточню: указание типа на шаге А не должно никак влиять на работу программы. Тогда, с одной стороны, язык остаётся тем же, а с другой — появляются способы проверить программу на наличие ошибок с типами до её запуска.
                • +7
                  Вы сейчас предлагаете сделать из динамического статический с помощью костылей и подпорок. Так как 90% кода с которым программист имеет дело это код библиотек языка и чужих библиотек, то реализация такой проверки лишь затруднит программирования и не даст нормального эффекта. По сути, вы предлагаете усложнить динамический язык определением типа, реализовать сложный парсер и статический анализатор и по сути все равно будите получать большую часть проблем динамического языка. Смысл если можно перейти на полностью статический или комбинированный (полустатический-полудинамический) язык?
                  • +1
                    > можно перейти
                    Если бы.
                    Но нет, абсурд дошёл до того, что сейчас библиотеки мы пишем на динамическом языке, а интерфейс к ним описываем на статическом. А кто-то делает это «статическими» комментариями с весьма странным синтаксисом. А кто-то вообще никак. И пока от этого нет спобоба уйти.
                    • +1
                      И пока от этого нет спобоба уйти

                      Кстати, меня давно интересует вопрос, а почему не ввести в конце концов в JS опциональные статические типы? Это же никак не сломает старый код, а жизнь программистов на Node.js и т.п. станет значительно удобнее. Кстати, это позволит уйти от нелогичных приведений типов, которые почти все JS программисты сильно недолюбливают (знаю потому что у меня самого JS второй язык и часто общался с чистыми JS разработчиками)
                      • +2
                        Статические типы и неявное приведение типов (слабая типизация) друг от друга никак не зависят. Например в Python типизация динамическая, но сильная.
                        • +4
                          А в C и C++ наоборот — статическая типизация, но слабая, т.е. с неявным приведением типов.
                          • 0
                            Именно. Некоторые вообще считают Си языком со статической абсолютно бессильной типизацией :)
                        • +1
                          Ввести-то можно, но надо заставить всего его повсеместно использовать, переписать весь DOM API, стандартную библиотеку и фреймворки, иначе это будет тот же TS: сами вы его при желании использовать можете, но типы будут только в своём коде.
                          +из-за обратной совместимости безопасность в рантайме гарантировать нельзя: в статический тип где-то сможет прийти что-то странное, это опять же надо обрабатывать.
                          Не думаю, что такое должно быть в стандарте. Как транслируемый язык вроде TS — вполне ок.
                    • +1
                      Вообще, NeoCode писал, что проверять соответствие типов ещё до запуска можно только в статически типизированных языках. Однако, это не так.


                      Однако, это так. Статические анализаторы для динамически типизируемых языков могут вывести тип правой части присваивания только в достаточно простых случаях. И то, только когда речь о полной доступности исполняемого кода.
                      • +1
                        Вообще, NeoCode писал, что проверять соответствие типов ещё до запуска можно только в статически типизированных языках. Однако, это не так. Просто в статических языках эта проверка входит в спецификацию языка, а для динамического — нет.
                        Как только вы добавите её в свой инструмент (неважно — запускаемый отдельно или прописанный в стандартной библиотеке как расширение) — вы получите статически типизированный язык.

                        Только смысла в этом нет. Статически типизированные языки хороши тем, что в ним программа состоит из «жёстких» кусков, где всё статически описано и чему можно верить. Ну и некоторое количество динамики там и сам (зыбкие места, где всё опасно и сложно — какой-нибудь парсер JSON'а типичный пример), которая опасна, но её мало, можно как-то смириться и аккуратно с ней разобраться. Когда же у вас в программе только маленькие кусочки описаны на статическом языке, то это общую картину мира меняет слабо.
                    • +4
                      Это будет интерпретируемый язык со статической (или почти статической) типизацией.
                      В отличие от этого, в современных статических языках динамика в каком-то виде уже встроена (полиморфизм в С++, система сообщений и динамические возможности ObjectiveC, dynamic в C#, any/variant в Boost, интерфейсы в Go и т.д.)
                      • 0
                        В статических языках появляются дополнительная возможность — типизация на этапе компиляции, которой нет в динамических.


                        Возьмём Common Lisp. Он компилируемый, при этом типизация в нём строгая, динамическая, с опциональными декларациями типов. Так вот в нём эта возможность есть.
                        • +1
                          при этом типизация в нём строгая, динамическая, с опциональными декларациями типов

                          С таким же успехом типизацию можно назвать строгой, статической с обширной системой динамических типов. На самом деле, это комбинированная типизация, которая не является ни полностью статической, ни полностью динамической, так что пример не подходит.
                          • +1
                            Но, следуя этой логике, C# тоже можно назвать языком с динамической строгой типизацией, с обширной системой статических типов. А значит, он не подходит для примера статического языка с динамическими возможностями.
                            • +1
                              Не подходит, поэтому не стоит рассматривать C# как пример чистого статического языка. Но в любом статическом языке есть возможность работать с Object и приводить Object к нужному интерфейсу и обратно к Object, этого достаточно для реализации любой динамической типизации. Да, при этом количества кода будет несколько больше чем в чистом динамическим языке, но динамическая типизация возможна в любом статическом языке (в ветке ниже описано как это сделать в 99.99% случаев).
                            • 0
                              И всё же она там не статическая. Типы вполне можно менять в рантайме. Более того, декларации типов нужны лишь чтобы получать более оптимизированный скомпилированный код. При несоответствии типов будут лишь ворнинги, хотя можно при возникновении ворнингов выдавать ошибку при сборке.
                          • 0
                            boost.variant имеет к «динамике» примерно такое же отношение, как возможность строкового типа хранить разные строки.

                            Про интерфейсы в Go не скажу, но их некоторая похожесть (судя по дискуссиям здесь) на хаскелевские тайпклассы также как бы намекает, что они вполне статические.
                          • +7
                            То есть, вы хотите сказать «Что мешает при использовании динамического языка сделать его статическим»? Да ничего, просто это же будет не динамический язык.
                          • +2
                            Мы же говорим не о математике, а о программировании.

                            Есть целый раздел математики, занимающийся типами. Так и называется, теория типов.

                            Там, кстати, и тоже отношения принадлежности есть, и связь с матлогикой, и вообще, если достаточно упороться, то оказывается, что можно на этом всю математику построить (по крайней мере, так кажется, сильно в HoTT я не вникал).
                            • 0
                              Есть. Но в данном случае я говорю о более простых вещах: множествах возможностей языков и отношениях между ними.
                            • +1
                              В динамически типизируемых языках появляется возможность менять типы переменных в рантайме, а не в статических возможность не менять. Компилятор статически типизируемого языка ограничивает набор допустимых выражений присваивания, ограничивает множество возможностей, доступных программисту.
                              • +5
                                Когда вам явно потребуется такая возможность, объявите переменную с типом Object и запихивайте в неё что хотите
                                • 0
                                  А как из этого Object вытащить данные\вызвать метод? Без рефлексии, разумеется.
                                  • +4
                                    когда понадобится, приведите к нужному интерфейсу — даже «вытаскивать» не понадобится
                                    • 0
                                      И как мы узнаем к какому именно интерфейсу приводить?
                                      • +4
                                        А как мы узнаем в динамическом языке что у данной переменной есть нужный метод? По большему счету нет большей разницы между:
                                        1. variable.myMethod()
                                        и
                                        2. interface MyMethodInterface { String myMethod()}
                                        ((MyMethodInterface) variable).myMethod()

                                        И в том и другом случае если метода myMethod нет или переменная на самом деле не реализует этот интерфейс — будет ошибка.
                                        P.S. Ну и не надо забывать про instanceOf оператор Java (и его аналоги в дргуих языках) позволяющий узнать какой реально тип сейчас в переменной и соотвестственно изменять логику.
                                        • –2
                                          Совершенно разные интерфейсы могут иметь методы\поля с одинаковым названием. Этих самых интерфейсов может быть бесчисленное множество + пользователь может создавать свои.

                                          Похоже не все понимают о чем идет речь, приведу пример. Пусть у нас есть 2 интерфейса:
                                          interface IProgram
                                          {
                                              void Execute();
                                          }
                                          
                                          interface IAction
                                          {
                                              void Execute();
                                          }
                                          

                                          Если делать так, как предлагаете вы, то в конечном итоге мы просто будем пытаться одурачить статическую типизацию жуткой пеленой if'ов:
                                          void Foo(object obj)
                                          {
                                              if (obj is IProgram)
                                              {
                                                  var program = (IProgram) obj;
                                                  program.Execute();
                                              }
                                              else if (obj is IAction)
                                              {
                                                  var action = (IAction) obj;
                                                  action.Execute();
                                              }
                                              else ... // и т.д. 1000 раз для всех остальных интерфейсов с методом Execute()
                                          }
                                          

                                          А если програмист написал еще один интерфейс:
                                          interface IPrisoner
                                          {
                                              void Execute();
                                          }
                                          
                                          IPrisoner prisoner = ...;
                                          Foo(prisoner); // ???
                                          

                                          Динамической типизации абсолютно по барабану какой интерфейс у объекта, важно лишь то, что объект должен иметь метод\делегат\функтор\whatever Execute().
                                          void Foo(dynamic obj)
                                          {
                                              obj.Execute();
                                          }
                                          
                                          • +5
                                            Надо просто объявить общий интерфейс IExecutable с единственным методом Execute(), а IProgram, IAction унаследовать от него. Так полностью соблюдается логика использования языка — контракт должен быть явно прописан.
                                            Попытки же вызвать у любого объекта метод с просто совпадающим названием — это потенциальная ошибка. Название может совпадать у принципиально разных и несовместимых методов. Явно определение всего стремиться минимизировать число таких слепых вызовов. Если уж вы точно знаете, что делаете то используйте рефлексию, но это явно не типичная ситуация.
                                            • –3
                                              Так и есть. В результате, мы всегда приходим к явно прописанному контракту, и в конечном итоге получаем криво-использованную-статическую-типизацию. Так что такое нельзя назвать заменой динамической типизации. А вот, например, dynamic в C# — можно.
                                              • +2
                                                Госсподя ты боже ж мой. Вопрос «сколько динамичности нам нужно в статически типизированных языках» он такой, филосовский: можно и полную динамику прикрутить, было бы желание… Да посмотрите на какие-нибудь сигнатуры в GNU C++, в конце-концов, а?

                                                Да, это, в конце-концов, было решено не вносить в стандарт и в последних версиях GCC сигнатуры не поддерживаются, но это ведь было частью самого «ортодоксального» из языков со статической типизацией! Причём в GNU C++ сигнатуры поддерживались уже тогда, когда не то, что о «dynamic в C#» никто не слышал, а и когда самого C# ещё не было!
                                                • 0
                                                  Не понимаю при чем здесь сигнатуры, и как они вообще связаны с динамической типизацией. Судя по вашей ссылке, это те же самые интерфейсы, только с утиной статической типизацией. Ну а шаблоны в C++ появились еще раньше сигнатур.

                                                  В то же время dynamic — это такой же ассоциативный массив, как например объекты в JS. Та что сравнение совершенно неуместно.

                                                  Но в любом случае я так и не понял к чему вы написали этот комментарий, без обид :) Я рассуждал о том, что «приведение к Object никак не может заменить динамическую типизацию».
                                                • +3
                                                  В результате, мы всегда приходим к явно прописанному контракту

                                                  Контракты в динамическом языке точно так же есть, вызывая переменную
                                                  var variable = getExecuteVariable();
                                                  variable.execute()
                                                  

                                                  вы требуете чтобы полученный динамический тип реализовывал метод execute(), то есть предполагаете контракт «метод getExecuteVariable() вернет тип, который содержит метод execute» иначе у вас все сломается. В статическом языке контракты просто прописаны явно (по правилу явное всегда лучше неявного).
                                                  Object variable = getExecuteVariable();
                                                  ((IExecutable) variable).execute()
                                                  

                                                  Причем если раньше ещё можно было относить к плюсам динамического языка скорость кодирования, то с развитием умных компиляторов с их быстрыми подстановками типов и увеличением размеров программ это уже мало существенно.

                                                  P.S. Динамический язык хорош для небольших скриптов и программ, проблема в том что динамические языки, которые разрабатывались для написания коротких программ, вроде JavaScript и Php сейчас используются для создания гиганских приложений, на что они изначально не были рассчитаны. Конечно, они не исчезнут (legacy и привычки программистов очень сложно меняются), но рано или поздно в них появиться статическая типизация и они станут комбинированными языками. ИМХО.
                                                  • +1
                                                    В конце-концов использование чего-нибудь вроде JavaScript для создания гигантских приложений приведет к тому, что появится среда разработки которая будет достаточно подробно анализировать код, чтобы выявлять все эти неявные контракты, знать какие на самом деле методы и переменные есть в конкретный момент у данной переменной и явно указывать на данные ошибки.
                                                    Но это по-факту приведет к тому, что мы сделаем JavaScript типизированным языком, просто типы будут прикручены грубыми костылями.
                                                    • 0
                                                      > приведет к тому…
                                                      Всё, что вы описали, уже есть, и даже круче: современные IDE пытаются найти свойства и без контрактов, и им это даже удаётся.
                                                      Я думаю, об этом и пишет автор: сейчас уже хочется опциональной поддержки типов, что де-факто уже и так используют, но хочется менее криво. Как в своё время в статические языки были добавлены фичи динамических.
                                                      • +1
                                                        и им это даже удаётся

                                                        Как-то не очень им удается. Простой пример
                                                        function foo(){
                                                            return 'foo'
                                                        }
                                                        function bar(){
                                                            return 'bar'
                                                        }
                                                        var a ={
                                                            foo:foo
                                                        }
                                                        var b ={
                                                            bar:bar
                                                        }
                                                        a=b;
                                                        
                                                        a.
                                                        

                                                        На последней строчке idea говорит, что у переменной есть функции как и foo, так и bar. Хотя на самом деле только bar.
                                                    • 0
                                                      В статическом языке контракты просто прописаны явно (по правилу явное всегда лучше неявного).

                                                      Да, я про это и говорю. Нам нужно явно привести объект к необходимому типу. Но для этого нужно знать тип. Да, если операторы is/instanceOf в C#/Java, но они лишь позволяют проверить на объект на соответствие определенному типу (который мы опять же указываем явно). Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию. Для обычных CLR объектов (которые не являются ExpandoObject) dynamic в конечном итоге приводит к рефлексии.

                                                      Честно говоря я не понимаю, почему у кого-то сложилось впечатление (судя по минусам не у вас одного) о том, что я сторонник динамической типизации. Нет-нет, я как раз таки всеми руками за статическую типизацию, собственно по тем же причинам, которые вы указали в P.S.
                                                      • 0
                                                        Тип мы и так известен — IExecutable. На объект наложен четкий контракт, что он должен быть исполняемым. Если уж вы так хотите исполнить метод у объекта который данный контракт не поддерживает, то не удивляйтесь что это приходиться делать через рефлексию.
                                                        • –1
                                                          Нету же никакого IExecutable.
                                                          • +3
                                                            Если вы так хотите несмотря ни на что вызывать метод у любого объекта, то используйте рефлексию. Чем она вас не устраивает?
                                                            А нормальный способ это создать общий интерфейс
                                                            • 0
                                                              А нормальный способ это создать общий интерфейс
                                                              Общий интерфейс мы можем сделать только для своего кода. В случае third-party бинарников или даже поделки коллег из соседнего отдела нужно или очень настойчиво попросить об этом автора или ildasm.

                                                              Чем она вас не устраивает?
                                                              Небольшая вырезка из моего предыдущего комментария:
                                                              Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию. Для обычных CLR объектов (которые не являются ExpandoObject) dynamic в конечном итоге приводит к рефлексии.
                                                              И только так. Если Borz считает иначе, пусть приведет пример. Для совершенно произвольного объекта, разумеется. Хоть буду знать, заслуженно ли я получил достаточно минусов (и в карму в том числе) за свою попытку проиллюстрировать его слова, или нет…
                                                              • +1
                                                                В случае third-party бинарников или даже поделки коллег из соседнего отдела нужно или очень настойчиво попросить об этом автора или ildasm.

                                                                Создаем пустые классы-наследники от классов third-party бинарников, которые реализуеют нужные нам интерфейсы и уже подобные wrapper классы, которые все реализуют один нужный нам интерфейс с методом execute.
                                                                То есть
                                                                    public class ThridPartyClass { // чужой класс
                                                                        public void execute() {
                                                                            // что делает
                                                                        }
                                                                    }
                                                                    
                                                                    public interface IExecuted {
                                                                        void execute();
                                                                    }
                                                                    public class ThridPartyClassWrapper extends ThridPartyClass implements IExecuted {        
                                                                    }
                                                                

                                                                ThridPartyClassWrapper вполне уже реализует IExecuted интерфейс и может использоваться для приведения типов.
                                                                • +1
                                                                  я и не говорил, что без рефлексии обойтись можно при для вызова метода. Я говорил как запихнуть произвольный объект в переменную. Использование рефлексии в статическом и динамическом языке только в том — используется она в нужный нам момент или везде где только придётся.

                                                                  А вызвать метод у произвольного объекта можно так (на основе вашего примера):
                                                                  public class Foo {
                                                                  	public Foo(Object obj) {
                                                                  		if (obj instanceof IExecutor) {
                                                                  			((IExecutor) obj).execute();
                                                                  			return;
                                                                  		}
                                                                  
                                                                  		// Тут пишем в лог ахтунг про "какого хера класс без нужного интерфейса?"
                                                                  		try {
                                                                  			final Method execute = obj.getClass().getDeclaredMethod("execute");
                                                                  			execute.invoke(obj);
                                                                  		} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                                                                  			e.printStackTrace();
                                                                  		}
                                                                  	}
                                                                  
                                                                  }
                                                                  
                                                                  interface IExecutor {
                                                                  	void execute();
                                                                  }
                                                                  

                                                                  Да, с рефлексией, но с точечной.

                                                                  P.S.: минусы не ставил
                                                              • +1
                                                                зато есть Callable и Runnable
                                                                • 0
                                                                  Они то здесь причем? И как их добавить в уже готовый бинарник?
                                                                  • 0
                                                                    я пропустил в разговоре критерий «уже готовый бинарник»?
                                                                    Я имел в виду, что вы можете свои IProgram, IAction и IPrisoner расширять от одного из этих интерфейсов, тем самым подписывая контракт на наличие необходимого метода.
                                                          • 0
                                                            В статическом языке контракты просто прописаны явно (по правилу явное всегда лучше неявного).
                                                            Да, я про это и говорю. Нам нужно явно привести объект к необходимому типу. Но для этого нужно знать тип. Да, если операторы is/instanceOf в C#/Java, но они лишь позволяют проверить на объект на соответствие определенному типу (который мы опять же указываем явно). Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию. Для обычных CLR объектов (которые не являются ExpandoObject) dynamic в конечном итоге приводит к рефлексии.

                                                            Честно говоря я не понимаю, почему у кого-то сложилось впечатление (судя по минусам не у вас одного) о том, что я сторонник динамической типизации. Нет-нет, я как раз таки всеми руками за статическую типизацию, собственно по тем же причинам, которые вы указали в P.S.
                                                            • +5
                                                              вызвать метод Execute() у совершенно произвольного объекта

                                                              Может привести хоть какой-то практический пример когда нужно вызвать метод у совершенно произвольного объекта? Так чтобы это не нарушало все принципы ООП и было хоть мало мальским оправданным с точки зрения качества кода?
                                                              Если мы говорим о своих объектах, то никто не мешает нам добавить им явный интерфейс, если мы используем чужие объекты с одинаковым методом, то фабрика, wrapper или proxy легко позволят привести их к одному виду. Ну и создавать публичный API, который принимает любой объект у которого есть метод Execute, тоже выглядит бредово. Честно говоря, не могу представить ни одного случая когда возможность «вызвать метод Execute() у совершенно произвольного объекта» было бы обоснованно, чем-то большим чем «так мы сможем наговнокодить на 5 минут быстрее».

                                                              Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию.

                                                              Нет, в Java можно ещё использовать кодегенерацию на лету, для отдельного объекта это дольше, но для огромного потока разных объектов — быстрее.
                                                              • 0
                                                                Может привести хоть какой-то практический пример когда нужно вызвать метод у совершенно произвольного объекта?
                                                                Разумеется нормальный разработчик такой хренью страдать и не будет. Он одумается намного раньше.
                                                                Нет, в Java можно ещё использовать кодегенерацию на лету, для отдельного объекта это дольше, но для огромного потока разных объектов — быстрее.
                                                                Не знаю как в Java, но в C# такой способ так или иначе сводится к рефлексии, ведь нам нужно достать MethodInfo в качестве операнда для инструкции callvirt.
                                                                • +2
                                                                  Почему нужно? Берем и пишем строку «pakage mypackage; public class Class1234 { public void execute(» + object.getClass() + " p) {" + object.getClass() + ".execute() };" закидываем её в класс loader одной из библиотек по генерации байт кода, генерим класс и вызываем class1234.execute(object); Для одиночного класса — дорого, но если каждый раз приходят тысячи и десятки тысяч классов каждого типа, то создав мапу <тип, сгенеренный класс> можно их выполнять намного быстрее рефлексии.
                                                                  • 0
                                                                    Для упоротых извращенцев можно просто эмиттить что угодно без рефлексии, достаточно знать опкод и строковое название метода.
                                                        • +1
                                                          А в скале есть structural types:
                                                          import scala.languageFeature.reflectiveCalls
                                                          
                                                          def executePoly(x: { def execute(): Unit }): Unit = {
                                                            x.execute()
                                                          }
                                                          
                                                          class A {
                                                            def execute(): Unit = { println("Hello from A") }
                                                          }
                                                          class B {
                                                            def execute(): Unit = { println("Hello from B") }
                                                          }
                                                          class C
                                                          
                                                          executePoly(new A)
                                                          executePoly(new B)
                                                          //Won't compile
                                                          //executePoly(new C)
                                                          

                                                          Конечно, так почти не делают (без нужды), но техническая возможность удобный duck typing занести есть :)
                                                        • 0
                                                          Примерно так мы узнаем в динамическом языке:

                                                          >>> a = 1
                                                          >>> b = '1'
                                                          >>> hasattr(a, '__add__')
                                                          True
                                                          >>> hasattr(b, '__add__')
                                                          True
                                                          >>> a + a
                                                          2
                                                          >>> b + b
                                                          '11'
                                                          >>> a + b
                                                          Traceback (most recent call last):
                                                          File "", line 1, in TypeError: unsupported operand type(s) for +: 'int' and 'str'
                                            • 0
                                              Однако аналог классов типов в динамических языках сложно сделать. Мультиметоды все-таки не совсем то, хочется диспечеризации по ожидаемому типу результата.
                                              • 0
                                                Может я чего не понимаю в колбасных обрезках, но «диспечеризации по ожидаемому типу результата» — это вообще редкость. Обычно речь идёт про разные типы параметров и в этом смысле C++ и Java ведут себя так же, как Python или, прости господи, PHP…
                                                • 0
                                                  Ну почему редкость, вполне типичная штука для хаскеля и, пожалуй, вполне реализуемая в любом System F\omega-языке, если не вообще в любом с выводом типов вроде Хиндли-Милнера.
                                                  • 0
                                                    Я и говорю — редкость. В скольки языках это есть из первых хотя бы 50 и сколько людей с ними «работают»? Только не говорите про C++ — там это есть в одном очень сильно исключительном месте. Если уж считать C++, то можно Perl засчитать…
                                                    • 0
                                                      А про какое место в C++ вы говорите?
                                                      • +2
                                                        Взятие адреса функции. Если одно и то же имя определяет много функций, то какой именно указатель вы получите зависит от того, куда вы захотите этот адрес засунуть (в сложных случаях можно сразу преобразовать имя фукнции к соответствующему указателю).
                                                        • 0
                                                          Можете показать пример? Во всех случаях, с которыми я сталкивался, надо было руками делать static_cast к нужной сигнатуре.
                                                          • +1
                                                            Можете показать пример?
                                                            Тут скорее нужно разбирать пример, когда это не работает. Но вот, пожалуйста:
                                                            cat test.cc 
                                                            #include <iostream>
                                                            
                                                            void foo(void (*p)(int)) {
                                                              (*p)(0);
                                                            }
                                                            
                                                            void bar(int) {
                                                              std::cout << "This is foo(int) !" << std::endl;
                                                            }
                                                            
                                                            void bar(float) {
                                                              std::cout << "This is foo(float) !" << std::endl;
                                                            }
                                                            
                                                            int main() {
                                                              foo(&bar);
                                                            }
                                                            $ g++ test.cc -o test
                                                            $ ./test
                                                            This is foo(int) !
                                                            

                                                            Во всех случаях, с которыми я сталкивался, надо было руками делать static_cast к нужной сигнатуре.
                                                            Эффект смещённой выборки, почти на 100%. Когда всё работает вы даже не задумываетесь над этим. Когда вдруг что-то не срабатывает — вы обращаете на проблему внимание. Скажем если бы foo была шаблонной функций и могла бы принять любой из двух адресов — это бы не сработало, нужен был бы static_cast — но static_cast не является чем-то исключительным, это просто одна из конструкций, которая гарантированно позволяет выбрать один из нескольких возможных вариантов!
                                                            • 0
                                                              А, действительно ведь
                                                              Эффект смещённой выборки, почти на 100%.

                                                              но потому, что почти не приходится использовать сырые указатели на функции.

                                                              У вас же тут прям и указана нужная аргумента в сигнатуре foo, так что получается в каком-то смысле эквивалентно.
                                                              • 0
                                                                У меня указан тип нужного мне аргумента, да. Но при этом выбирается не одна из возможных функций foo, а наоборот — одна из возможных функций bar!
                                                              • 0
                                                                Но вот, пожалуйста:

                                                                Я, конечно, все понимаю, но это — диспетчеризация по типу параметра, а не по типу результата. Про тип результата мне самому очень интересно узнать.
                                                                • –1
                                                                  Для того чтобы понять, что вы неправы нужно уметь считать до двух.

                                                                  Вас, наверное, сбила с тольку похожесть этого примера на традиционный:
                                                                  cat test.cc 
                                                                  #include <iostream>
                                                                  
                                                                  void foo(int) {
                                                                    std::cout << "This is foo(int) !" << std::endl;
                                                                  }
                                                                  
                                                                  void foo(float) {
                                                                    std::cout << "This is foo(float) !" << std::endl;
                                                                  }
                                                                  
                                                                  int main() {
                                                                    foo(1.f);
                                                                  }
                                                                  $ g++ test.cc -o test
                                                                  $ ./test
                                                                  This is foo(float) !
                                                                  

                                                                  Они действительно очень похожи, но, обратите внимание, в первом случае у нас одна функция foo (хотя две функции bar), а во втором — их две. Подумайте над этим на досуге…
                                                                  • +1
                                                                    Эмм, а какая разница, сколько функций foo? У вас есть некая переменная, ее тип — void (*p)(int), вы присваете ей значение &bar (то, что оно потом передается в функцию уже, не важно). Выбор между bar происходит на основании того, что у одной из них параметр — int, а у другой — float. Я где-то не прав, я что-то не вижу?
                                                                    • 0
                                                                      Вы не видите (или не хотите видеть) очевидного: у &bar нет значения. Оно появляется когда вы &bar куда-нибудь засовываете. Во всех других местах в языке выражение x = y тип x никак не будет влиять на значение y, а тут будет.

                                                                      Можно этот пример привести к такому:
                                                                      Всякие объявления
                                                                      $ cat test.cc
                                                                      #include <iostream>
                                                                      
                                                                      void foo(void (*p)(int)) {
                                                                        (*p)(0);
                                                                      }
                                                                      
                                                                      void z(int) {
                                                                        std::cout << "This is foo(int) !" << std::endl;
                                                                      }
                                                                      
                                                                      void z(float) {
                                                                        std::cout << "This is foo(float) !" << std::endl;
                                                                      }
                                                                      
                                                                      int main() {
                                                                        void (*x)(int);
                                                                        void (*y)(float);
                                                                      

                                                                        x = z;
                                                                        y = z;
                                                                        std::cout << "x= " << (void *)x << std::endl;
                                                                        std::cout << "y= " << (void *)y << std::endl;
                                                                      }
                                                                      $ g++ test.cc -o test
                                                                      $ ./test
                                                                      x= 0x4008b6
                                                                      y= 0x4008df
                                                                      
                                                                      Вот как бы когда то, что слева от знака присваивания влияет на значение того что справа — это и есть «диспечеризации по ожидаемому типу результата». И в C++ она вот именно в этом месте есть. Даже если вы не хотите этого видеть. Что оная диспечеризация тесно связана с фукнциями и их параметрами — это тоже правда, но ведь в этом примере мы вообще никаких параметров никуда не передаём…
                                                                      • +2
                                                                        Вы не видите (или не хотите видеть) очевидного: у &bar нет значения.

                                                                        Как это нет? А что присваивается влево?

                                                                        Вот как бы когда то, что справа влияет на значение того что слева — это «диспечеризации по ожидаемому типу результата».

                                                                        А, понятно, очередная терминологическая путаница. Для меня диспетчеризация по типу результата — это вот так:

                                                                        int bar()
                                                                        {
                                                                          return 0;
                                                                        }
                                                                        
                                                                        string bar()
                                                                        {
                                                                          return "q";
                                                                        }
                                                                        
                                                                        int a = bar(); //0
                                                                        string b = bar(); //q
                                                                        
                                                                        • 0
                                                                          Не понял где путаница.

                                                                          Если для вас важны int и string, тогда, конечно, ничего не поделать. В C++ все эти чудеса происходят только и исключительно с указателями на функцию. Но если хочется просто функцию вызвать, тогда это… всегда пожалуйста:

                                                                          Всякие объявления
                                                                          $ cat test.cc
                                                                          #include <iostream>
                                                                          
                                                                          void foo(int) {
                                                                            std::cout << "This is foo(int) !" << std::endl;
                                                                          }
                                                                          
                                                                          void foo(float) {
                                                                            std::cout << "This is foo(float) !" << std::endl;
                                                                          }
                                                                          
                                                                          struct FOO {
                                                                            typedef void (*PI)(int);
                                                                            typedef void (*PF)(float);
                                                                            operator PI() { return &foo; }
                                                                            operator PF() { return &foo; }
                                                                          };
                                                                          
                                                                          struct FOO bar() {
                                                                            return FOO();
                                                                          }
                                                                          
                                                                          int main() {
                                                                            void (*x)(int);
                                                                            void (*y)(float);
                                                                          

                                                                            x = bar();
                                                                            y = bar();
                                                                            std::cout << "x= " << (void *)x << std::endl;
                                                                            std::cout << "y= " << (void *)y << std::endl;
                                                                          }
                                                                          khim@khim-x1:/tmp/2$ g++ test.cc -o test
                                                                          khim@khim-x1:/tmp/2$ ./test
                                                                          x= 0x40089d
                                                                          y= 0x4008c6
                                                                          
                                                                          • 0
                                                                            Если для вас важны int и string, тогда, конечно, ничего не поделать.

                                                                            Для меня важен overload resolution. И в С++

                                                                            Return types are not considered in overload resolution.
                                                                            • 0
                                                                              overload resolution касается только одного очень частого случая выражения: вызова функции. И возвращаемые из функции выражения там действительно нигде участвовать не могут.

                                                                              Но кроме вызова функций в C++ есть целая куча разных видов выражений. И у них у большинства есть, представляете, результаты. И в некоторых случаях таки ожидаемый тип результата влияет на значение выражения.

                                                                              Если происходит не вызов функции, а, скажем присваивание — то тип результата может влиять на значение выражения. И при возврате значения из функции с помощью return — тоже может. И ещё в нескольких местах. Во всех этих местах происходит выбор одного указателя из нескольких возможных.

                                                                              С чего вы решили, что я говорю именно про вызов функции — для меня загадка.
                                                                              • 0
                                                                                С чего вы решили, что я говорю именно про вызов функции — для меня загадка

                                                                                Вот поэтому я и говорю — терминологическая путаница.
                                              • +3
                                                Динамический язык — это по сути статический, в котором из типов есть только «any».

                                                Неправда. Тип подразумевает множество значений и операции над ними. Если в динамических языках только 1 тип, то как в них возможен полиформизм?
                                                class A(object):
                                                    def foo(self, a):
                                                        print('A', a)
                                                
                                                
                                                class B(A):
                                                    pass
                                                
                                                
                                                class C(A):
                                                    def foo(self, a):
                                                        print('C', a)
                                                
                                                b = B()
                                                b.foo(1) # A 1
                                                c = C()
                                                c.foo(2) # C 2
                                                


                                                Ну и

                                                >>> abs('-1')
                                                Traceback (most recent call last):
                                                  File "<stdin>", line 1, in <module>
                                                TypeError: bad operand type for abs(): 'str'
                                                


                                                Просто в динамических языках тип определяется в рантайме, а в статических — на этапе компиляции. Они не подмножества/надмножества относительно друг друга.
                                                • +2
                                                  В том же С++ тоже есть полиморфизм и тоже типы могут определяться в рантайме.
                                                  То есть полиморфизм тут вообще не при чем.
                                                  • –1
                                                    Что вы имеете ввиду под «определяться»?
                                                    • 0
                                                      Нет, серьёзно, что такое «определяться»-то?

                                                      Максимум — виртуальные функции с диспатчингом вызова в конкретную реализацию через соответствующую таблицу, но это несерьёзно называть «определением типов в рантайме».
                                                  • +3
                                                    Полиморфизм элементарно реализуется в языках с тремя типами — число, хеш-таблица, и строка. Посмотри на JavaScript.
                                                  • 0
                                                    Тип any/dynamic на деле не всегда означает динамическое устройство

                                                    c#
                                                    dynamic x = "a";
                                                    x++;
                                                    

                                                    Runtime error
                                                    • +2
                                                      Но ведь скомпилируется! А переопределить поведение в этом случае технически вполне возможно, только скорее всего никому не нужно.
                                                      Думаю, если в динамическом языке написать какую-нибудь фигню вроде инкремента сложного нечислового объекта, то тоже будет Runtime error.
                                                      • +1
                                                        Эта вот «фигня» для всех разная. Для меня, например, «5 + 2 + 'a'» тоже «фигня». Поэтому и есть сильная/слабая, статическая/динамическая типизация. В случае с первыми «фигня» строго задекларирована и не нужно гадать. Это про ту самую «неопределённость» из статьи.
                                                        • 0
                                                          В вашем примере именно строгая динамическая типизация. Как в питоне, например.
                                                          • 0
                                                            В вашем примере именно строгая динамическая типизация. Как в питоне, например.
                                                            Даже если и так, что с того? Я лишь написал, что мне это не нравится («фигня»).
                                                            А это, как раз пример слабой типизации, а к статике/динамике вообще отношения нет.
                                                            Например в c# этот код скомпилируется, за счет неявного приведения, но конечный тип известен.

                                                            И вот еще, открыл песочницу с питоном, написал туда
                                                            print(5 + 2 + "a")
                                                            

                                                            Вывод
                                                            TypeError: unsupported operand type(s) for +: 'int' and 'str'

                                                            Потому что в питоне, как уже замечено, типизация строгая.
                                                            • +1
                                                              А можно в питоне что вернет функция getIsEmailCorrect(«fff@mail.ru») чужого класса, если нет документации и код внутри черт ногу сломит? Я вот по названию могу предположить что он может вернуть как boolean, так и integer (1 — корректный, -1 — не корректный), String («правильно»/«не правильно») и даже какой-то Object. Иногда можно запустить код и проверить, а если это очень сложно (код работающий с базой данных/сетью/очень много сложных аргуементов и прочее) или банально долго?
                                                              • 0
                                                                Так ведь есть же статическая типизация. И тип возвращаемого значения можно указать и не подменит этот тип никто.
                                                              • +1
                                                                На один шаг дальше:

                                                                a = 0
                                                                for x in [5, 2, "a"]:
                                                                    a += x
                                                                print(a)
                                                                #
                                                                print(sum([5, 2, "a"]))
                                                                

                                                                ещё на один шаг дальше:

                                                                for doc in db.x.find({}):
                                                                    a += doc['x']
                                                                


                                                                И с каждым таким шагом отладка становится всё веселее и веселее
                                                                • 0
                                                                  Согласен, поэтому я и пишу, что мне это не нравится.
                                                            • +2
                                                              Не обязательно, представьте миллион вызов функций getId() в коде из разных чужих классов. Во всех случаях, с которыми проверил getId() возвращает числовое значение, а в продакшене возьмет и вернет String из одного класса, потому что в одном из чужих классов решили что значение «не определенно» для id — корректное и у вас все упадет. Проблема в том что пока выполнение до этого класса не дойдет, вы не узнаете что он конкретно вернет (если не лазить по его внутренностям и это большими буквами не написано в его документации).
                                                              • 0
                                                                Так вот нет. В случае статической типизации, ты можешь явно указать контракт возвращаемого значения. Например, в c# указав int, ты никогда не получишь null или строку и т.д. Всегда будет число.
                                                                • +2
                                                                  Можно, только с# это не динамический язык, по крайне мере не полностью динамический. Если ты пишешь String getId() — то что тут динамического? В scala тоже можно определять переменную как var result = 0, она же не считается динамическим языком.
                                                                  • 0
                                                                    Где я пишу, что c# динамический язык?
                                                                    • +1
                                                                      Так зачем приводить его в подтверждения возможностей «строгой динамической типизации»?
                                                                      • 0
                                                                        Я не приводил пример «строгой динамической типизации»
                                                                        Более того, «5 + 2 + 'a'» относится к строгости/слабости. К статике/динамике отношения это не имеет.
                                                                        И этим примером я хотел сказать, что из увиденного не очевидно, что будет на выходе. Я не намекал ни на систему типов, ни на конкретный язык.
                                                        • 0
                                                          Редкий статический язык поддерживает возможность описать функцию от Int, а передать в нее Any, предполагая что оно на самом деле целое.
                                                        • +28
                                                          Бессмысленный холивар, который опубликовал кто-то за бугром, это что прибавляет авторитетности статье?
                                                          • +3
                                                            А я вот интерпретируемые языки не люблю. И что? Кого это интересует?
                                                            • +4
                                                              >Знаете, какой DSL лучше всего описывает HTML? Я открою вам секрет: это HTML

                                                              Не стоит забывать что HTML (вместе с CSS и JS) — это прежде всего формат сериализации DOM. Ну и дополнительно — формат общения с поисковиками.

                                                              И, внезапно, чтобы писать веб-приложения — не обязательно использовать html! У меня вот веб-проект на React, который почти не использует html.

                                                              Что также удивительно — html довольно плохой формат сериализации DOM-а.
                                                              • +5
                                                                Сдается мне это самообман. Ваш замечательный веб-проект отдает клиентскому браузеру именно HTML.
                                                                Да, а компилятор gcc выдает двоичные инструкции для процессора, и при этом программисту не нужно ничего о них знать — скажете вы.
                                                                Это верно, вот только HTML, в отличие от процессорных инструкций, постоянно развивается и рано или поздно вам может понадобиться залезть в самую глубину вашего React и что-то там подправить в генерации HTML-кода. буквально пару аттрибутов. А потом еще и еще.
                                                                • –1
                                                                  Ну это спорно.
                                                                  Вы эти изменения сможете инкапсулировать в созданных вами React компонентах нижнего уровня. Остальные же компоненты даже об этом не узнают.
                                                                  А возьмем проект на чистом HTML. Изменения затронут весь код.
                                                                  • 0
                                                                    Реакт-код попадает в браузер в виде JS-кода. Который потом напрямую строит DOM, посредством реакта. Т.е. рендеринг минует стадию HTML.
                                                                    • 0
                                                                      И как все это дружит с миллионом технологий кэширования страниц, картинок, стилей? Просто «всё то же самое, но своё» прикручивается как-то сбоку скриптами, кастомными хранилищами и прочим?..
                                                                      • 0
                                                                        Статика (включая сам JS-код) нормально кэшируется и на клиенте, и на сервере, поскольку это сетевой уровень, никакого отношения к HTML и DOM не имеющий. Более того, с различными клиент-сайд html-шаблонизаторами и DOM-генераторами кэширование зачастую лучше работает на динамических страницах (а их подавляющее большинство сейчас субъективно), поскольку статическая часть страницы передаётся только один раз, а не рендерится на сервере на каждый запрос, а по сети гуляют только данные с минимальным оверхидом.
                                                                      • 0
                                                                        Как всё это дружит с SEO?
                                                                    • 0
                                                                      А какой хороший?

                                                                      Я так понимаю, что HTML язык разметки. Т.е. стояла такая задача, что есть у нас текст и надо его разметить — привязать какие-то атрибуты к кусочкам чтоб получился гипертекст.

                                                                      Причем, наверное, вначале обычно пользовались просто текстовыми редакторами. Поэтому он такой. Какой-нибудь yaml было бы неудобно так использовать, т.к. надо было бы много в тексте менять. А тут вносишь локальную правку и все — кусочек помечен как ссылка, например.

                                                                      • 0
                                                                        Как язык разметки гипертекста — HTML неплох. Речь не об этом.

                                                                        Была фраза: «Знаете, какой DSL лучше всего описывает HTML? Я открою вам секрет: это HTML». И тут есть логическая ошибка.

                                                                        Получить HTML — не цель. Цель — получить DOM. А исходные данные — не гипертекст, а какая-то декларативная модель UI (кнопочки, блоки), и только внизу там где-то DIV-ы, завязанные на CSS и JS.

                                                                        Поэтому применительно к шаблонам логика про «юзайте HTML в шаблонах» — не работает. А критикуемые автором eDSL — отлично работают.
                                                                    • +1
                                                                      Мы хотели получить пусть большое и страшное, но генерируемое компилятором сообщение об ошибке, когда мы попытаемся записать null в булевое поле.

                                                                      То ли я чего-то не понимаю, то ли автор:
                                                                      object foo {
                                                                        def foo(a: Boolean): Unit = {
                                                                          println(a)
                                                                        }
                                                                      
                                                                        def bar(num: Int): Boolean = {
                                                                          println("Hello from bar")
                                                                          if (num == 0) {
                                                                            return null
                                                                          }
                                                                          true
                                                                        }
                                                                      
                                                                        def main(args: Array[String]) {
                                                                          foo(null)
                                                                          foo(bar(1))
                                                                          foo(bar(0))
                                                                        }
                                                                      }
                                                                      

                                                                      При компиляции выдаёт ошибки.

                                                                      Ну и для проблем с null вроде как есть Optional.

                                                                      Даже для Python есть «type hints»

                                                                      Это сделали не для того, чтобы добавить подобие статической типизации, а для более простого анализа кода в ИДЕ и подобными штуками. Что, в свою очередь, нужно в основном для документирования. Например, есть некая функция, которая принимает аргумент с именем phone. Ты не знаешь, какой тип аргумента с именем phone подразумевался автором. Если прописать в определении функции тип аргумента, то при зажатом ctrl при наведении на вызов этой функции, PyCharm покажет, какой тип нужен. В принципе, PyCharm мог это делать и через docstring, но там были свои ограничения на подсказку типа.

                                                                      Вот вам пример: предметно-ориентированные языки (DSL) — это способ решения проблем, или ещё один способ их создания?

                                                                      А при чём тут типизация? DSL можно и на статических языках делать.

                                                                      Кто из нас не страдал от метапрограммирования в Ruby или мэпов в Clojure?

                                                                      Опять же, причём тут типизация? Страдать от метапрограммирования можно на любом языке, который умеет его. Через интроспекцию и в java можно проблем себе создать. Чтобы не страдать, достаточно лишь использовать метапрограммирование с умом.
                                                                      • +5
                                                                        Это сделали не для того, чтобы добавить подобие статической типизации, а для более простого анализа кода в ИДЕ и подобными штуками.


                                                                        Это типизация, так как значению приписывается тип и при этом она статическая, так как инструментам надо его знать до выполнения.

                                                                        Через интроспекцию и в java можно проблем себе создать.


                                                                        Как раз проблема интроспекции именно в том, что она динамически типизированная.
                                                                        • 0
                                                                          > Это типизация, так как значению приписывается тип и при этом она статическая, так как инструментам надо его знать до выполнения.

                                                                          Типизация в Питоне уже есть, она динамическая и PEP на неё никак не повлиял. Никакой тип значению не приписывается: чтобы вы не написали в аннотациях к параметрам, тип переменных от этого не изменится никак. Инструментам не надо знать тип до выполнения: аннотации могут помочь IDE лучше (чем сейчас) угадывать тип переменной для удобства программиста, но не более того. Даже если вы везде пропишете точные type hints, не всегда будет возможно точно определить тип любой переменной в принципе.
                                                                          • 0
                                                                            Значит теперь в Питоне две типизации — динамическая для рантайма и компилятора и статическая для IDE, причем они необязательно между собой согласованы.

                                                                            Инструментам не надо знать тип до выполнения: аннотации могут помочь IDE лучше (чем сейчас) угадывать тип переменной для удобства программиста, но не более того


                                                                            Получается, чтобы предоставить какой-то уровень удобства им именно надо знать тип до выполнения. То есть это типизация и при этом статическая.
                                                                            • +2
                                                                              Ну тогда у питона давно две типизации, ведь в docstring тоже можно типы прописывать. И в js их 2. И в php тоже 2.
                                                                              • 0
                                                                                Если есть формальные правила аннотации, то согласен.
                                                                              • 0
                                                                                Это не статическая типизация, а попытка угадать тип без выполнения.
                                                                                • +2
                                                                                  Где «угадать тип» == типизация, а «без выполнения» == статическая
                                                                                  • +2
                                                                                    Угадать — это не типизация. Нарушение типизации вызывает ошибку, а при угадывании в лучшем случае варнинг.
                                                                                    • +1
                                                                                      Поискал в википедии — там типизация == проверка статус сообщений о нарушении правил не указан

                                                                                      Исходный коммент был про «подобие» типизации. Я лично нахожу в статической проверке типов отличающийся только статусом сообщения много подобия :)
                                                                                      • 0
                                                                                        Стати́ческая типиза́ция — приём, широко используемый в языках программирования, при котором переменная, параметр подпрограммы, возвращаемое значение функции связывается с типом в момент объявления и тип не может быть изменён позже (переменная или параметр будут принимать, а функция — возвращать значения только этого типа)


                                                                                        Если тип не может быть изменён, то попытка его изменить вызывает ошибку. Если он всё же изменяется, пускай и с варнингом, то это не статическая типизация.

                                                                                        • +1
                                                                                          А что тот тип, который внутри себя приписывает редактор переменной по тайпхинту может быть изменен в рантайме?
                                                                                        • +1
                                                                                          То есть C++ и С это языки с динамической типизацией? Ведь там никто не мешает программисту взять и записать int в первый байт переменной double и вообще устроить ад с любыми типами через указатели.
                                                                                          • +1
                                                                                            Переменная всё равно останется double.
                                                                                          • 0
                                                                                            Такая же ситуация и с Common Lisp: даже если задекларировать типы, компилятор лишь выдаст варнинг при их несоответствии. Декларация там нужна, чтобы компилировать более оптимизированный код. Типизация там от декларации типов не становится статической.
                                                                          • +1
                                                                            Знаете, какой DSL лучше всего описывает HTML? Я открою вам секрет: это HTML.
                                                                            Не всегда: необходимость закрывающих тэгов периодически доставляет неудовольствие…
                                                                            • –2
                                                                              HTML хорош тем, что его можно просто отдать верстальщику, не заставляя его учить самописные недоязыки.
                                                                              • 0
                                                                                Мой коллега-верстальщик пилит React-компоненты самостоятельно, фронтенд-программистам остаётся только бизнес-логика.
                                                                          • +1
                                                                            Не могу не прорекламировать Red http://www.red-lang.org/
                                                                            на мой взгляд крайне интересный язык
                                                                            • 0
                                                                              Ну кто ж так рекламирует? Вы бы хоть описали вкратце чем он так интересен :-)
                                                                            • +4
                                                                              Бредятина. Чел экстраполирует личные фейлы на целый мир.
                                                                              • +10
                                                                                Есть среди фанатов статической типизации такая традиция: каждый год они собираются и хоронят языки с динамической типизацией. Ну-ну.

                                                                                image
                                                                                • +2
                                                                                  Ну, Джаваскрипт поднялся на 1 место, при этом Руби и Питон опустились на 2, а Джава поднялась на 5 мест и догоняет Джаваскрипт, у которого приличная фора.
                                                                                  • +2
                                                                                    Скорее аудитория C плавно перетекает Java, все остальное в мире стабильно
                                                                                    • +4
                                                                                      Кто это с си на джаву перетекает? С плюсов уж скорее, но там уже кто мог, уже перетёк. Джава скорее отражает популярность андроида.
                                                                                      • +2
                                                                                        Видимо из C уходят в С++, а из С++ на Java и C#. Поэтому создается впечатление что на C++ все стабильно.
                                                                                        • +1
                                                                                          Вообще было бы правильно смотреть на данные и по ним делать выводы.
                                                                                          Интересно, у гитхаба можно вытащить данные по всем коммитам человека за определенный период, либо хотя-бы за-star-енные репозитории? Если да, то можно было бы построить весьма интересный датасет.
                                                                                • +6
                                                                                  PHP сама стабильность. Прикольно звучит)
                                                                                  • +8
                                                                                    Что удивительно они их правильно хоронят. Есть определённый предел сложности программ, которые можно реализовать на динамических языках. Вот когда люди его достигают — они вдруг «прозревают» и уходят в статические языки (или пристраивают костыли к динамическим языкам чтобы они стали статически типизированными (всякие TypeScript, Closure и прочее).

                                                                                    Но при этом «прозревшие» забывают о том, что подавляющее большинство программистов по-прежнемц решают гораздо более простые задачи и у них нужды в статически типизированных языках не возникает! Что, собственно, и показывает ваш граф…
                                                                                    • +1
                                                                                      Кстати, интересно почему в динамические языки вроде Php и JS, которые давно подбираются к этому пределу сложности все-таки не вводят новые опциональные статические типы? По-моему, это вполне логичный шаг для них получить комбинированную типизацию…
                                                                                      • +4
                                                                                        PHP давно последовательно вводит некое подобие статической типизации — тайп хинтинг в сигнатурах функций и методов. Описав типы параметров разработчик функции или метода может быть уверен, что они будут данного типа (в строгом режиме вызов функции вызовет ошибку, в обычном — типы приведутся к указанным), то же с возвращаемым значением функции — увидев что функция возвращает строку я уверен, что не придёт ни число, ни массив — транслятор этого не допустит.
                                                                                        • +2
                                                                                          О, спасибо. Жалко что ничто подобное пока не реализовано в JS.
                                                                                          • +2
                                                                                            Вообще тайп-хинты в JS наверняка помогли бы реализовать гораздо более эффективный рантайм, а значит мы бы сразу увидели преимущество TypeScript над JS в плане первоманса