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. Нам нужно больше языков подобного типа.
Мы думаем, что видели в жизни уже всё, что нет в программировании новых идей, которые были бы способны удивить. Хуже того — мы отказываемся даже изучить что-то, что не согласуется с нашим предыдущим опытом. Ну, знаете, вот это «Боже, посмотрите только на синтаксис!». Но вспомните, как тяжело вам было тогда, когда вы учили свой первый язык программирования — там что, было понятнее? Но ведь оно того стоило, правда? Не нужно бояться проделать это захватывающее путешествие ещё раз.
Теолог Герхард Вос однажды описал человеческую жизнь как «уже, но всё ещё нет». С его точки зрения верующим людям судьбой предопределено счастье в загробной жизни, но вот пока ещё им приходится мириться с мирскими бедами и тягостями. Эта метафора хорошо описывает и то, где сейчас находимся мы, программисты. Да, у нас пока нет идеальных языков программирования и действительно умных инструментов, но с моей точки зрения блистательное будущее этой сферы человеческой деятельности уже предопределено. Многие из сегодняшних разработок уже идут по правильному пути, а чему-то ещё только предстоит появиться. Нам ещё предстоит длинная дорога, но мы определённо сможем её пройти.
Комментарии (242)
NeoCode
26.11.2015 13:49-3Вполне логично. Динамические языки это просто подмножество статических. Во многих статических языках вполне могут существовать типы any (dynamic) для «истинно динамического типа данных» и variant для суммы типов из фиксированного набора (алгебраический тип данных).
Динамический язык — это по сути статический, в котором из типов есть только «any».dmitry_hidden
26.11.2015 14:24+9по-мойму как раз таки наоборот. Динамические языки имеют более общей смысл, как в математике натуральные числа являются подмножеством целых.
NeoCode
26.11.2015 15:33+1Мы же говорим не о математике, а о программировании.
Но даже если и сравнить с числами — никакого более общего смысла в динамических языках нет. В статических языках появляются дополнительная возможность — типизация на этапе компиляции, которой нет в динамических. При этом в современных статических языках есть все возможности из динамических. То есть этой типизацией можно пользоваться, а можно и не пользоваться. То есть динамические имеют меньше возможностей по сравнению с современными статическими (хотя их правильнее наверное называть комбинированными, т.к. они сочетают и динамику и статику), и поэтому яляются подмножеством. Чистая теория множеств:)bromzh
26.11.2015 16:29-3А что при использовании динамического языка мешает:
а) указывать тип переменной каким-то образом (доки, спец. средства, вроде type hints для питона и т.п.)
б) сделать анализатор кода, который будет пытаться выводить тип переменных на основе подсказок о типе
в) запускать анализатор для всего проекта перед запуском самого проекта (и назвать эту операцию «компиляция»).
г) при ошибке анализатора о несоответствии типов запретить запускать программу
Понятно, что сделать анализатор кода для динамического языка намного труднее. Но и статический анализ для статических языков неидеален.terryP
26.11.2015 16:51+10что при использовании динамического языка мешает
Ничего, кроме того что вы предлагаете самому написать новый статический язык с новыми правилами… По сути, вы предлагаете самому написать аналог TypeScript для JavaScript, это, конечно, возможно, но смысл?bromzh
26.11.2015 17:45Но в моём случае это будет не другой язык. а тот же самый. И этап В в моём случае можно отключить, при этом программа будет работать.
Вообще, NeoCode писал, что проверять соответствие типов ещё до запуска можно только в статически типизированных языках. Однако, это не так. Просто в статических языках эта проверка входит в спецификацию языка, а для динамического — нет.
Уточню: указание типа на шаге А не должно никак влиять на работу программы. Тогда, с одной стороны, язык остаётся тем же, а с другой — появляются способы проверить программу на наличие ошибок с типами до её запуска.terryP
26.11.2015 18:33+7Вы сейчас предлагаете сделать из динамического статический с помощью костылей и подпорок. Так как 90% кода с которым программист имеет дело это код библиотек языка и чужих библиотек, то реализация такой проверки лишь затруднит программирования и не даст нормального эффекта. По сути, вы предлагаете усложнить динамический язык определением типа, реализовать сложный парсер и статический анализатор и по сути все равно будите получать большую часть проблем динамического языка. Смысл если можно перейти на полностью статический или комбинированный (полустатический-полудинамический) язык?
Antelle
27.11.2015 10:47+1> можно перейти
Если бы.
Но нет, абсурд дошёл до того, что сейчас библиотеки мы пишем на динамическом языке, а интерфейс к ним описываем на статическом. А кто-то делает это «статическими» комментариями с весьма странным синтаксисом. А кто-то вообще никак. И пока от этого нет спобоба уйти.terryP
27.11.2015 12:12+1И пока от этого нет спобоба уйти
Кстати, меня давно интересует вопрос, а почему не ввести в конце концов в JS опциональные статические типы? Это же никак не сломает старый код, а жизнь программистов на Node.js и т.п. станет значительно удобнее. Кстати, это позволит уйти от нелогичных приведений типов, которые почти все JS программисты сильно недолюбливают (знаю потому что у меня самого JS второй язык и часто общался с чистыми JS разработчиками)VolCh
27.11.2015 12:19+2Статические типы и неявное приведение типов (слабая типизация) друг от друга никак не зависят. Например в Python типизация динамическая, но сильная.
Antelle
27.11.2015 12:36+1Ввести-то можно, но надо заставить всего его повсеместно использовать, переписать весь DOM API, стандартную библиотеку и фреймворки, иначе это будет тот же TS: сами вы его при желании использовать можете, но типы будут только в своём коде.
+из-за обратной совместимости безопасность в рантайме гарантировать нельзя: в статический тип где-то сможет прийти что-то странное, это опять же надо обрабатывать.
Не думаю, что такое должно быть в стандарте. Как транслируемый язык вроде TS — вполне ок.
VolCh
26.11.2015 19:47+1Вообще, NeoCode писал, что проверять соответствие типов ещё до запуска можно только в статически типизированных языках. Однако, это не так.
Однако, это так. Статические анализаторы для динамически типизируемых языков могут вывести тип правой части присваивания только в достаточно простых случаях. И то, только когда речь о полной доступности исполняемого кода.
khim
27.11.2015 02:39+1Вообще, NeoCode писал, что проверять соответствие типов ещё до запуска можно только в статически типизированных языках. Однако, это не так. Просто в статических языках эта проверка входит в спецификацию языка, а для динамического — нет.
Как только вы добавите её в свой инструмент (неважно — запускаемый отдельно или прописанный в стандартной библиотеке как расширение) — вы получите статически типизированный язык.
Только смысла в этом нет. Статически типизированные языки хороши тем, что в ним программа состоит из «жёстких» кусков, где всё статически описано и чему можно верить. Ну и некоторое количество динамики там и сам (зыбкие места, где всё опасно и сложно — какой-нибудь парсер JSON'а типичный пример), которая опасна, но её мало, можно как-то смириться и аккуратно с ней разобраться. Когда же у вас в программе только маленькие кусочки описаны на статическом языке, то это общую картину мира меняет слабо.
NeoCode
26.11.2015 16:52+4Это будет интерпретируемый язык со статической (или почти статической) типизацией.
В отличие от этого, в современных статических языках динамика в каком-то виде уже встроена (полиморфизм в С++, система сообщений и динамические возможности ObjectiveC, dynamic в C#, any/variant в Boost, интерфейсы в Go и т.д.)bromzh
26.11.2015 18:06В статических языках появляются дополнительная возможность — типизация на этапе компиляции, которой нет в динамических.
Возьмём Common Lisp. Он компилируемый, при этом типизация в нём строгая, динамическая, с опциональными декларациями типов. Так вот в нём эта возможность есть.terryP
26.11.2015 18:36+1при этом типизация в нём строгая, динамическая, с опциональными декларациями типов
С таким же успехом типизацию можно назвать строгой, статической с обширной системой динамических типов. На самом деле, это комбинированная типизация, которая не является ни полностью статической, ни полностью динамической, так что пример не подходит.bromzh
26.11.2015 20:42+1Но, следуя этой логике, C# тоже можно назвать языком с динамической строгой типизацией, с обширной системой статических типов. А значит, он не подходит для примера статического языка с динамическими возможностями.
terryP
27.11.2015 12:07+1Не подходит, поэтому не стоит рассматривать C# как пример чистого статического языка. Но в любом статическом языке есть возможность работать с Object и приводить Object к нужному интерфейсу и обратно к Object, этого достаточно для реализации любой динамической типизации. Да, при этом количества кода будет несколько больше чем в чистом динамическим языке, но динамическая типизация возможна в любом статическом языке (в ветке ниже описано как это сделать в 99.99% случаев).
bromzh
27.11.2015 14:32И всё же она там не статическая. Типы вполне можно менять в рантайме. Более того, декларации типов нужны лишь чтобы получать более оптимизированный скомпилированный код. При несоответствии типов будут лишь ворнинги, хотя можно при возникновении ворнингов выдавать ошибку при сборке.
0xd34df00d
29.11.2015 16:43boost.variant имеет к «динамике» примерно такое же отношение, как возможность строкового типа хранить разные строки.
Про интерфейсы в Go не скажу, но их некоторая похожесть (судя по дискуссиям здесь) на хаскелевские тайпклассы также как бы намекает, что они вполне статические.
newcommer
26.11.2015 20:30+7То есть, вы хотите сказать «Что мешает при использовании динамического языка сделать его статическим»? Да ничего, просто это же будет не динамический язык.
0xd34df00d
26.11.2015 16:32+2Мы же говорим не о математике, а о программировании.
Есть целый раздел математики, занимающийся типами. Так и называется, теория типов.
Там, кстати, и тоже отношения принадлежности есть, и связь с матлогикой, и вообще, если достаточно упороться, то оказывается, что можно на этом всю математику построить (по крайней мере, так кажется, сильно в HoTT я не вникал).NeoCode
26.11.2015 16:48Есть. Но в данном случае я говорю о более простых вещах: множествах возможностей языков и отношениях между ними.
VolCh
26.11.2015 19:43+1В динамически типизируемых языках появляется возможность менять типы переменных в рантайме, а не в статических возможность не менять. Компилятор статически типизируемого языка ограничивает набор допустимых выражений присваивания, ограничивает множество возможностей, доступных программисту.
Borz
27.11.2015 00:07+5Когда вам явно потребуется такая возможность, объявите переменную с типом Object и запихивайте в неё что хотите
dordzhiev
27.11.2015 00:55А как из этого Object вытащить данные\вызвать метод? Без рефлексии, разумеется.
Borz
27.11.2015 00:59+4когда понадобится, приведите к нужному интерфейсу — даже «вытаскивать» не понадобится
dordzhiev
27.11.2015 01:40И как мы узнаем к какому именно интерфейсу приводить?
terryP
27.11.2015 02:21+4А как мы узнаем в динамическом языке что у данной переменной есть нужный метод? По большему счету нет большей разницы между:
1. variable.myMethod()
и
2. interface MyMethodInterface { String myMethod()}
((MyMethodInterface) variable).myMethod()
И в том и другом случае если метода myMethod нет или переменная на самом деле не реализует этот интерфейс — будет ошибка.
P.S. Ну и не надо забывать про instanceOf оператор Java (и его аналоги в дргуих языках) позволяющий узнать какой реально тип сейчас в переменной и соотвестственно изменять логику.dordzhiev
27.11.2015 03:51-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(); }
intet
27.11.2015 08:51+5Надо просто объявить общий интерфейс IExecutable с единственным методом Execute(), а IProgram, IAction унаследовать от него. Так полностью соблюдается логика использования языка — контракт должен быть явно прописан.
Попытки же вызвать у любого объекта метод с просто совпадающим названием — это потенциальная ошибка. Название может совпадать у принципиально разных и несовместимых методов. Явно определение всего стремиться минимизировать число таких слепых вызовов. Если уж вы точно знаете, что делаете то используйте рефлексию, но это явно не типичная ситуация.dordzhiev
27.11.2015 09:59-3Так и есть. В результате, мы всегда приходим к явно прописанному контракту, и в конечном итоге получаем криво-использованную-статическую-типизацию. Так что такое нельзя назвать заменой динамической типизации. А вот, например, dynamic в C# — можно.
khim
27.11.2015 10:33+2Госсподя ты боже ж мой. Вопрос «сколько динамичности нам нужно в статически типизированных языках» он такой, филосовский: можно и полную динамику прикрутить, было бы желание… Да посмотрите на какие-нибудь сигнатуры в GNU C++, в конце-концов, а?
Да, это, в конце-концов, было решено не вносить в стандарт и в последних версиях GCC сигнатуры не поддерживаются, но это ведь было частью самого «ортодоксального» из языков со статической типизацией! Причём в GNU C++ сигнатуры поддерживались уже тогда, когда не то, что о «dynamic в C#» никто не слышал, а и когда самого C# ещё не было!dordzhiev
27.11.2015 20:36Не понимаю при чем здесь сигнатуры, и как они вообще связаны с динамической типизацией. Судя по вашей ссылке, это те же самые интерфейсы, только с утиной статической типизацией. Ну а шаблоны в C++ появились еще раньше сигнатур.
В то же время dynamic — это такой же ассоциативный массив, как например объекты в JS. Та что сравнение совершенно неуместно.
Но в любом случае я так и не понял к чему вы написали этот комментарий, без обид :) Я рассуждал о том, что «приведение к Object никак не может заменить динамическую типизацию».
terryP
27.11.2015 11:41+3В результате, мы всегда приходим к явно прописанному контракту
Контракты в динамическом языке точно так же есть, вызывая переменную
var variable = getExecuteVariable(); variable.execute()
вы требуете чтобы полученный динамический тип реализовывал метод execute(), то есть предполагаете контракт «метод getExecuteVariable() вернет тип, который содержит метод execute» иначе у вас все сломается. В статическом языке контракты просто прописаны явно (по правилу явное всегда лучше неявного).
Object variable = getExecuteVariable(); ((IExecutable) variable).execute()
Причем если раньше ещё можно было относить к плюсам динамического языка скорость кодирования, то с развитием умных компиляторов с их быстрыми подстановками типов и увеличением размеров программ это уже мало существенно.
P.S. Динамический язык хорош для небольших скриптов и программ, проблема в том что динамические языки, которые разрабатывались для написания коротких программ, вроде JavaScript и Php сейчас используются для создания гиганских приложений, на что они изначально не были рассчитаны. Конечно, они не исчезнут (legacy и привычки программистов очень сложно меняются), но рано или поздно в них появиться статическая типизация и они станут комбинированными языками. ИМХО.intet
27.11.2015 17:25+1В конце-концов использование чего-нибудь вроде JavaScript для создания гигантских приложений приведет к тому, что появится среда разработки которая будет достаточно подробно анализировать код, чтобы выявлять все эти неявные контракты, знать какие на самом деле методы и переменные есть в конкретный момент у данной переменной и явно указывать на данные ошибки.
Но это по-факту приведет к тому, что мы сделаем JavaScript типизированным языком, просто типы будут прикручены грубыми костылями.Antelle
27.11.2015 22:32> приведет к тому…
Всё, что вы описали, уже есть, и даже круче: современные IDE пытаются найти свойства и без контрактов, и им это даже удаётся.
Я думаю, об этом и пишет автор: сейчас уже хочется опциональной поддержки типов, что де-факто уже и так используют, но хочется менее криво. Как в своё время в статические языки были добавлены фичи динамических.intet
28.11.2015 08:32+1и им это даже удаётся
Как-то не очень им удается. Простой пример
function foo(){ return 'foo' } function bar(){ return 'bar' } var a ={ foo:foo } var b ={ bar:bar } a=b; a.
На последней строчке idea говорит, что у переменной есть функции как и foo, так и bar. Хотя на самом деле только bar.
dordzhiev
27.11.2015 20:45В статическом языке контракты просто прописаны явно (по правилу явное всегда лучше неявного).
Да, я про это и говорю. Нам нужно явно привести объект к необходимому типу. Но для этого нужно знать тип. Да, если операторы is/instanceOf в C#/Java, но они лишь позволяют проверить на объект на соответствие определенному типу (который мы опять же указываем явно). Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию. Для обычных CLR объектов (которые не являются ExpandoObject) dynamic в конечном итоге приводит к рефлексии.
Честно говоря я не понимаю, почему у кого-то сложилось впечатление (судя по минусам не у вас одного) о том, что я сторонник динамической типизации. Нет-нет, я как раз таки всеми руками за статическую типизацию, собственно по тем же причинам, которые вы указали в P.S.intet
27.11.2015 21:46Тип мы и так известен — IExecutable. На объект наложен четкий контракт, что он должен быть исполняемым. Если уж вы так хотите исполнить метод у объекта который данный контракт не поддерживает, то не удивляйтесь что это приходиться делать через рефлексию.
dordzhiev
27.11.2015 21:50-1Нету же никакого IExecutable.
intet
27.11.2015 22:10+3Если вы так хотите несмотря ни на что вызывать метод у любого объекта, то используйте рефлексию. Чем она вас не устраивает?
А нормальный способ это создать общий интерфейсdordzhiev
28.11.2015 03:18А нормальный способ это создать общий интерфейс
Общий интерфейс мы можем сделать только для своего кода. В случае third-party бинарников или даже поделки коллег из соседнего отдела нужно или очень настойчиво попросить об этом автора или ildasm.
Чем она вас не устраивает?
Небольшая вырезка из моего предыдущего комментария:Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию. Для обычных CLR объектов (которые не являются ExpandoObject) dynamic в конечном итоге приводит к рефлексии.
И только так. Если Borz считает иначе, пусть приведет пример. Для совершенно произвольного объекта, разумеется. Хоть буду знать, заслуженно ли я получил достаточно минусов (и в карму в том числе) за свою попытку проиллюстрировать его слова, или нет…terryP
28.11.2015 03:45+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 интерфейс и может использоваться для приведения типов.
Borz
28.11.2015 11:32+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.: минусы не ставил
Borz
27.11.2015 22:40+1dordzhiev
28.11.2015 03:19Они то здесь причем? И как их добавить в уже готовый бинарник?
Borz
28.11.2015 11:26я пропустил в разговоре критерий «уже готовый бинарник»?
Я имел в виду, что вы можете свои IProgram, IAction и IPrisoner расширять от одного из этих интерфейсов, тем самым подписывая контракт на наличие необходимого метода.
dordzhiev
27.11.2015 20:47В статическом языке контракты просто прописаны явно (по правилу явное всегда лучше неявного).
Да, я про это и говорю. Нам нужно явно привести объект к необходимому типу. Но для этого нужно знать тип. Да, если операторы is/instanceOf в C#/Java, но они лишь позволяют проверить на объект на соответствие определенному типу (который мы опять же указываем явно). Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию. Для обычных CLR объектов (которые не являются ExpandoObject) dynamic в конечном итоге приводит к рефлексии.
Честно говоря я не понимаю, почему у кого-то сложилось впечатление (судя по минусам не у вас одного) о том, что я сторонник динамической типизации. Нет-нет, я как раз таки всеми руками за статическую типизацию, собственно по тем же причинам, которые вы указали в P.S.terryP
27.11.2015 22:24+5вызвать метод Execute() у совершенно произвольного объекта
Может привести хоть какой-то практический пример когда нужно вызвать метод у совершенно произвольного объекта? Так чтобы это не нарушало все принципы ООП и было хоть мало мальским оправданным с точки зрения качества кода?
Если мы говорим о своих объектах, то никто не мешает нам добавить им явный интерфейс, если мы используем чужие объекты с одинаковым методом, то фабрика, wrapper или proxy легко позволят привести их к одному виду. Ну и создавать публичный API, который принимает любой объект у которого есть метод Execute, тоже выглядит бредово. Честно говоря, не могу представить ни одного случая когда возможность «вызвать метод Execute() у совершенно произвольного объекта» было бы обоснованно, чем-то большим чем «так мы сможем наговнокодить на 5 минут быстрее».
Единственный способ вызвать метод Execute() у совершенно произвольного объекта — использовать рефлексию.
Нет, в Java можно ещё использовать кодегенерацию на лету, для отдельного объекта это дольше, но для огромного потока разных объектов — быстрее.dordzhiev
28.11.2015 03:33Может привести хоть какой-то практический пример когда нужно вызвать метод у совершенно произвольного объекта?
Разумеется нормальный разработчик такой хренью страдать и не будет. Он одумается намного раньше.
Нет, в Java можно ещё использовать кодегенерацию на лету, для отдельного объекта это дольше, но для огромного потока разных объектов — быстрее.
Не знаю как в Java, но в C# такой способ так или иначе сводится к рефлексии, ведь нам нужно достать MethodInfo в качестве операнда для инструкции callvirt.terryP
28.11.2015 03:58+2Почему нужно? Берем и пишем строку «pakage mypackage; public class Class1234 { public void execute(» + object.getClass() + " p) {" + object.getClass() + ".execute() };" закидываем её в класс loader одной из библиотек по генерации байт кода, генерим класс и вызываем class1234.execute(object); Для одиночного класса — дорого, но если каждый раз приходят тысячи и десятки тысяч классов каждого типа, то создав мапу <тип, сгенеренный класс> можно их выполнять намного быстрее рефлексии.
PsyHaSTe
03.12.2015 14:54Для упоротых извращенцев можно просто эмиттить что угодно без рефлексии, достаточно знать опкод и строковое название метода.
Ivanhoe
27.11.2015 11:11+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 занести есть :)
BlessMaster
27.11.2015 08:36Примерно так мы узнаем в динамическом языке:
>>> 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'
potan
01.12.2015 12:46Однако аналог классов типов в динамических языках сложно сделать. Мультиметоды все-таки не совсем то, хочется диспечеризации по ожидаемому типу результата.
khim
01.12.2015 14:31Может я чего не понимаю в колбасных обрезках, но «диспечеризации по ожидаемому типу результата» — это вообще редкость. Обычно речь идёт про разные типы параметров и в этом смысле C++ и Java ведут себя так же, как Python или, прости господи, PHP…
0xd34df00d
01.12.2015 17:41Ну почему редкость, вполне типичная штука для хаскеля и, пожалуй, вполне реализуемая в любом System F\omega-языке, если не вообще в любом с выводом типов вроде Хиндли-Милнера.
khim
01.12.2015 18:49Я и говорю — редкость. В скольки языках это есть из первых хотя бы 50 и сколько людей с ними «работают»? Только не говорите про C++ — там это есть в одном очень сильно исключительном месте. Если уж считать C++, то можно Perl засчитать…
0xd34df00d
01.12.2015 18:53А про какое место в C++ вы говорите?
khim
01.12.2015 20:09+2Взятие адреса функции. Если одно и то же имя определяет много функций, то какой именно указатель вы получите зависит от того, куда вы захотите этот адрес засунуть (в сложных случаях можно сразу преобразовать имя фукнции к соответствующему указателю).
0xd34df00d
02.12.2015 15:13Можете показать пример? Во всех случаях, с которыми я сталкивался, надо было руками делать
static_cast
к нужной сигнатуре.khim
02.12.2015 17:45+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) !
Во всех случаях, с которыми я сталкивался, надо было руками делать
Эффект смещённой выборки, почти на 100%. Когда всё работает вы даже не задумываетесь над этим. Когда вдруг что-то не срабатывает — вы обращаете на проблему внимание. Скажем если быstatic_cast
к нужной сигнатуре.foo
была шаблонной функций и могла бы принять любой из двух адресов — это бы не сработало, нужен был быstatic_cast
— ноstatic_cast
не является чем-то исключительным, это просто одна из конструкций, которая гарантированно позволяет выбрать один из нескольких возможных вариантов!0xd34df00d
02.12.2015 17:49А, действительно ведь
Эффект смещённой выборки, почти на 100%.
но потому, что почти не приходится использовать сырые указатели на функции.
У вас же тут прям и указана нужная аргумента в сигнатуреfoo
, так что получается в каком-то смысле эквивалентно.khim
02.12.2015 18:10У меня указан тип нужного мне аргумента, да. Но при этом выбирается не одна из возможных функций
foo
, а наоборот — одна из возможных функцийbar
!
lair
02.12.2015 17:56Но вот, пожалуйста:
Я, конечно, все понимаю, но это — диспетчеризация по типу параметра, а не по типу результата. Про тип результата мне самому очень интересно узнать.khim
02.12.2015 18:17-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
), а во втором — их две. Подумайте над этим на досуге…lair
02.12.2015 18:23+1Эмм, а какая разница, сколько функций
foo
? У вас есть некая переменная, ее тип —void (*p)(int)
, вы присваете ей значение&bar
(то, что оно потом передается в функцию уже, не важно). Выбор междуbar
происходит на основании того, что у одной из них параметр —int
, а у другой —float
. Я где-то не прав, я что-то не вижу?khim
02.12.2015 18:46Вы не видите (или не хотите видеть) очевидного: у
&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);
lair
02.12.2015 18:49+2Вы не видите (или не хотите видеть) очевидного: у
&bar
нет значения.
Как это нет? А что присваивается влево?
Вот как бы когда то, что справа влияет на значение того что слева — это «диспечеризации по ожидаемому типу результата».
А, понятно, очередная терминологическая путаница. Для меня диспетчеризация по типу результата — это вот так:
int bar() { return 0; } string bar() { return "q"; } int a = bar(); //0 string b = bar(); //q
khim
02.12.2015 19:06Не понял где путаница.
Если для вас важны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);
lair
02.12.2015 20:44Если для вас важны int и string, тогда, конечно, ничего не поделать.
Для меня важен overload resolution. И в С++
Return types are not considered in overload resolution.
khim
02.12.2015 23:14overload resolution касается только одного очень частого случая выражения: вызова функции. И возвращаемые из функции выражения там действительно нигде участвовать не могут.
Но кроме вызова функций в C++ есть целая куча разных видов выражений. И у них у большинства есть, представляете, результаты. И в некоторых случаях таки ожидаемый тип результата влияет на значение выражения.
Если происходит не вызов функции, а, скажем присваивание — то тип результата может влиять на значение выражения. И при возврате значения из функции с помощьюreturn
— тоже может. И ещё в нескольких местах. Во всех этих местах происходит выбор одного указателя из нескольких возможных.
С чего вы решили, что я говорю именно про вызов функции — для меня загадка.lair
02.12.2015 23:17С чего вы решили, что я говорю именно про вызов функции — для меня загадка
Вот поэтому я и говорю — терминологическая путаница.
bromzh
26.11.2015 14:53+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'
Просто в динамических языках тип определяется в рантайме, а в статических — на этапе компиляции. Они не подмножества/надмножества относительно друг друга.NeoCode
26.11.2015 15:27+2В том же С++ тоже есть полиморфизм и тоже типы могут определяться в рантайме.
То есть полиморфизм тут вообще не при чем.0xd34df00d
26.11.2015 16:31-1Что вы имеете ввиду под «определяться»?
0xd34df00d
29.11.2015 16:44Нет, серьёзно, что такое «определяться»-то?
Максимум — виртуальные функции с диспатчингом вызова в конкретную реализацию через соответствующую таблицу, но это несерьёзно называть «определением типов в рантайме».
jakobz
26.11.2015 15:58+3Полиморфизм элементарно реализуется в языках с тремя типами — число, хеш-таблица, и строка. Посмотри на JavaScript.
Serg046
26.11.2015 14:53Тип any/dynamic на деле не всегда означает динамическое устройство
c#
dynamic x = "a"; x++;
Runtime errorNeoCode
26.11.2015 15:40+2Но ведь скомпилируется! А переопределить поведение в этом случае технически вполне возможно, только скорее всего никому не нужно.
Думаю, если в динамическом языке написать какую-нибудь фигню вроде инкремента сложного нечислового объекта, то тоже будет Runtime error.Serg046
26.11.2015 15:53+1Эта вот «фигня» для всех разная. Для меня, например, «5 + 2 + 'a'» тоже «фигня». Поэтому и есть сильная/слабая, статическая/динамическая типизация. В случае с первыми «фигня» строго задекларирована и не нужно гадать. Это про ту самую «неопределённость» из статьи.
ApeCoder
26.11.2015 16:48В вашем примере именно строгая динамическая типизация. Как в питоне, например.
Serg046
26.11.2015 16:59В вашем примере именно строгая динамическая типизация. Как в питоне, например.
Даже если и так, что с того? Я лишь написал, что мне это не нравится («фигня»).
А это, как раз пример слабой типизации, а к статике/динамике вообще отношения нет.
Например в c# этот код скомпилируется, за счет неявного приведения, но конечный тип известен.
И вот еще, открыл песочницу с питоном, написал туда
print(5 + 2 + "a")
Вывод
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Потому что в питоне, как уже замечено, типизация строгая.terryP
26.11.2015 17:16+1А можно в питоне что вернет функция getIsEmailCorrect(«fff@mail.ru») чужого класса, если нет документации и код внутри черт ногу сломит? Я вот по названию могу предположить что он может вернуть как boolean, так и integer (1 — корректный, -1 — не корректный), String («правильно»/«не правильно») и даже какой-то Object. Иногда можно запустить код и проверить, а если это очень сложно (код работающий с базой данных/сетью/очень много сложных аргуементов и прочее) или банально долго?
Serg046
26.11.2015 17:28Так ведь есть же статическая типизация. И тип возвращаемого значения можно указать и не подменит этот тип никто.
BlessMaster
27.11.2015 08:01+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']
И с каждым таким шагом отладка становится всё веселее и веселее
vedenin1980
26.11.2015 17:02+2Не обязательно, представьте миллион вызов функций getId() в коде из разных чужих классов. Во всех случаях, с которыми проверил getId() возвращает числовое значение, а в продакшене возьмет и вернет String из одного класса, потому что в одном из чужих классов решили что значение «не определенно» для id — корректное и у вас все упадет. Проблема в том что пока выполнение до этого класса не дойдет, вы не узнаете что он конкретно вернет (если не лазить по его внутренностям и это большими буквами не написано в его документации).
Serg046
26.11.2015 17:10Так вот нет. В случае статической типизации, ты можешь явно указать контракт возвращаемого значения. Например, в c# указав int, ты никогда не получишь null или строку и т.д. Всегда будет число.
vedenin1980
26.11.2015 17:15+2Можно, только с# это не динамический язык, по крайне мере не полностью динамический. Если ты пишешь String getId() — то что тут динамического? В scala тоже можно определять переменную как var result = 0, она же не считается динамическим языком.
Serg046
26.11.2015 17:17Где я пишу, что c# динамический язык?
vedenin1980
26.11.2015 17:20+1Так зачем приводить его в подтверждения возможностей «строгой динамической типизации»?
Serg046
26.11.2015 17:25Я не приводил пример «строгой динамической типизации»
Более того, «5 + 2 + 'a'» относится к строгости/слабости. К статике/динамике отношения это не имеет.
И этим примером я хотел сказать, что из увиденного не очевидно, что будет на выходе. Я не намекал ни на систему типов, ни на конкретный язык.
potan
01.12.2015 12:42Редкий статический язык поддерживает возможность описать функцию от Int, а передать в нее Any, предполагая что оно на самом деле целое.
dim_s
26.11.2015 14:26+28Бессмысленный холивар, который опубликовал кто-то за бугром, это что прибавляет авторитетности статье?
jakobz
26.11.2015 15:08+4>Знаете, какой DSL лучше всего описывает HTML? Я открою вам секрет: это HTML
Не стоит забывать что HTML (вместе с CSS и JS) — это прежде всего формат сериализации DOM. Ну и дополнительно — формат общения с поисковиками.
И, внезапно, чтобы писать веб-приложения — не обязательно использовать html! У меня вот веб-проект на React, который почти не использует html.
Что также удивительно — html довольно плохой формат сериализации DOM-а.ncix
26.11.2015 16:42+5Сдается мне это самообман. Ваш замечательный веб-проект отдает клиентскому браузеру именно HTML.
Да, а компилятор gcc выдает двоичные инструкции для процессора, и при этом программисту не нужно ничего о них знать — скажете вы.
Это верно, вот только HTML, в отличие от процессорных инструкций, постоянно развивается и рано или поздно вам может понадобиться залезть в самую глубину вашего React и что-то там подправить в генерации HTML-кода. буквально пару аттрибутов. А потом еще и еще.wheercool
26.11.2015 18:43-1Ну это спорно.
Вы эти изменения сможете инкапсулировать в созданных вами React компонентах нижнего уровня. Остальные же компоненты даже об этом не узнают.
А возьмем проект на чистом HTML. Изменения затронут весь код.
jakobz
26.11.2015 21:03Реакт-код попадает в браузер в виде JS-кода. Который потом напрямую строит DOM, посредством реакта. Т.е. рендеринг минует стадию HTML.
PsyHaSTe
03.12.2015 14:58И как все это дружит с миллионом технологий кэширования страниц, картинок, стилей? Просто «всё то же самое, но своё» прикручивается как-то сбоку скриптами, кастомными хранилищами и прочим?..
VolCh
04.12.2015 07:24Статика (включая сам JS-код) нормально кэшируется и на клиенте, и на сервере, поскольку это сетевой уровень, никакого отношения к HTML и DOM не имеющий. Более того, с различными клиент-сайд html-шаблонизаторами и DOM-генераторами кэширование зачастую лучше работает на динамических страницах (а их подавляющее большинство сейчас субъективно), поскольку статическая часть страницы передаётся только один раз, а не рендерится на сервере на каждый запрос, а по сети гуляют только данные с минимальным оверхидом.
ApeCoder
26.11.2015 16:46А какой хороший?
Я так понимаю, что HTML язык разметки. Т.е. стояла такая задача, что есть у нас текст и надо его разметить — привязать какие-то атрибуты к кусочкам чтоб получился гипертекст.
Причем, наверное, вначале обычно пользовались просто текстовыми редакторами. Поэтому он такой. Какой-нибудь yaml было бы неудобно так использовать, т.к. надо было бы много в тексте менять. А тут вносишь локальную правку и все — кусочек помечен как ссылка, например.
jakobz
26.11.2015 21:18Как язык разметки гипертекста — HTML неплох. Речь не об этом.
Была фраза: «Знаете, какой DSL лучше всего описывает HTML? Я открою вам секрет: это HTML». И тут есть логическая ошибка.
Получить HTML — не цель. Цель — получить DOM. А исходные данные — не гипертекст, а какая-то декларативная модель UI (кнопочки, блоки), и только внизу там где-то DIV-ы, завязанные на CSS и JS.
Поэтому применительно к шаблонам логика про «юзайте HTML в шаблонах» — не работает. А критикуемые автором eDSL — отлично работают.
bromzh
26.11.2015 15:59+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 можно проблем себе создать. Чтобы не страдать, достаточно лишь использовать метапрограммирование с умом.ApeCoder
26.11.2015 16:51+5Это сделали не для того, чтобы добавить подобие статической типизации, а для более простого анализа кода в ИДЕ и подобными штуками.
Это типизация, так как значению приписывается тип и при этом она статическая, так как инструментам надо его знать до выполнения.
Через интроспекцию и в java можно проблем себе создать.
Как раз проблема интроспекции именно в том, что она динамически типизированная.veveve
26.11.2015 21:54> Это типизация, так как значению приписывается тип и при этом она статическая, так как инструментам надо его знать до выполнения.
Типизация в Питоне уже есть, она динамическая и PEP на неё никак не повлиял. Никакой тип значению не приписывается: чтобы вы не написали в аннотациях к параметрам, тип переменных от этого не изменится никак. Инструментам не надо знать тип до выполнения: аннотации могут помочь IDE лучше (чем сейчас) угадывать тип переменной для удобства программиста, но не более того. Даже если вы везде пропишете точные type hints, не всегда будет возможно точно определить тип любой переменной в принципе.ApeCoder
27.11.2015 09:53Значит теперь в Питоне две типизации — динамическая для рантайма и компилятора и статическая для IDE, причем они необязательно между собой согласованы.
Инструментам не надо знать тип до выполнения: аннотации могут помочь IDE лучше (чем сейчас) угадывать тип переменной для удобства программиста, но не более того
Получается, чтобы предоставить какой-то уровень удобства им именно надо знать тип до выполнения. То есть это типизация и при этом статическая.VolCh
27.11.2015 12:22Это не статическая типизация, а попытка угадать тип без выполнения.
ApeCoder
27.11.2015 13:35+2Где «угадать тип» == типизация, а «без выполнения» == статическая
VolCh
27.11.2015 13:53+2Угадать — это не типизация. Нарушение типизации вызывает ошибку, а при угадывании в лучшем случае варнинг.
ApeCoder
27.11.2015 13:59+1Поискал в википедии — там типизация == проверка статус сообщений о нарушении правил не указан
Исходный коммент был про «подобие» типизации. Я лично нахожу в статической проверке типов отличающийся только статусом сообщения много подобия :)VolCh
27.11.2015 14:07Стати?ческая типиза?ция — приём, широко используемый в языках программирования, при котором переменная, параметр подпрограммы, возвращаемое значение функции связывается с типом в момент объявления и тип не может быть изменён позже (переменная или параметр будут принимать, а функция — возвращать значения только этого типа)
Если тип не может быть изменён, то попытка его изменить вызывает ошибку. Если он всё же изменяется, пускай и с варнингом, то это не статическая типизация.
bromzh
27.11.2015 14:27Такая же ситуация и с Common Lisp: даже если задекларировать типы, компилятор лишь выдаст варнинг при их несоответствии. Декларация там нужна, чтобы компилировать более оптимизированный код. Типизация там от декларации типов не становится статической.
Flammar
26.11.2015 18:40+1Знаете, какой DSL лучше всего описывает HTML? Я открою вам секрет: это HTML.
Не всегда: необходимость закрывающих тэгов периодически доставляет неудовольствие…
veveve
26.11.2015 21:32+10Есть среди фанатов статической типизации такая традиция: каждый год они собираются и хоронят языки с динамической типизацией. Ну-ну.
VolCh
26.11.2015 22:03+2Ну, Джаваскрипт поднялся на 1 место, при этом Руби и Питон опустились на 2, а Джава поднялась на 5 мест и догоняет Джаваскрипт, у которого приличная фора.
SerCe
26.11.2015 23:42+2Скорее аудитория C плавно перетекает Java, все остальное в мире стабильно
Athari
27.11.2015 02:50+4Кто это с си на джаву перетекает? С плюсов уж скорее, но там уже кто мог, уже перетёк. Джава скорее отражает популярность андроида.
deniskreshikhin
27.11.2015 11:48+2Видимо из C уходят в С++, а из С++ на Java и C#. Поэтому создается впечатление что на C++ все стабильно.
SerCe
27.11.2015 15:43+1Вообще было бы правильно смотреть на данные и по ним делать выводы.
Интересно, у гитхаба можно вытащить данные по всем коммитам человека за определенный период, либо хотя-бы за-star-енные репозитории? Если да, то можно было бы построить весьма интересный датасет.deniskreshikhin
27.11.2015 15:47Да можно, у них абсолютно открытый и прозрачный API. Как и у stackoverflow.
khim
27.11.2015 02:48+8Что удивительно они их правильно хоронят. Есть определённый предел сложности программ, которые можно реализовать на динамических языках. Вот когда люди его достигают — они вдруг «прозревают» и уходят в статические языки (или пристраивают костыли к динамическим языкам чтобы они стали статически типизированными (всякие TypeScript, Closure и прочее).
Но при этом «прозревшие» забывают о том, что подавляющее большинство программистов по-прежнемц решают гораздо более простые задачи и у них нужды в статически типизированных языках не возникает! Что, собственно, и показывает ваш граф…terryP
27.11.2015 12:20+1Кстати, интересно почему в динамические языки вроде Php и JS, которые давно подбираются к этому пределу сложности все-таки не вводят новые опциональные статические типы? По-моему, это вполне логичный шаг для них получить комбинированную типизацию…
VolCh
27.11.2015 12:58+4PHP давно последовательно вводит некое подобие статической типизации — тайп хинтинг в сигнатурах функций и методов. Описав типы параметров разработчик функции или метода может быть уверен, что они будут данного типа (в строгом режиме вызов функции вызовет ошибку, в обычном — типы приведутся к указанным), то же с возвращаемым значением функции — увидев что функция возвращает строку я уверен, что не придёт ни число, ни массив — транслятор этого не допустит.
terryP
27.11.2015 12:59+2О, спасибо. Жалко что ничто подобное пока не реализовано в JS.
SerCe
27.11.2015 15:45+2Вообще тайп-хинты в JS наверняка помогли бы реализовать гораздо более эффективный рантайм, а значит мы бы сразу увидели преимущество TypeScript над JS в плане первоманса
googol
02.12.2015 09:32Closure Compiler от Гугла ввел подобие типов для того чтобы js-to-js компилятор мог побольше соптимизировать developers.google.com/closure/compiler/docs/js-for-compiler?hl=en
NeoCode
27.11.2015 11:45А разве html и css это языки программирования? ИМХО это языки разметки (в общем случае — декларативного описания данных), вообще совершенно другая категория, ближе ко всяким ini-файлам, документам rtf, pdf и т.п.
terryP
27.11.2015 11:58+2Конечно, нет. Другое дело что в разных top'ах языки разметки и языки программирования часто смешивают. Кстати в том же заголовке есть слово язык, но нет слова программирования.
Lol4t0
27.11.2015 00:55+1На самом деле все дело привычки. Когда много пишешь на языках со статической типизацией, сложно переключиться на языки с динамической, и наоборот.
Статически типизированные ЯП могут приблизиться по элегантности и и краткости в части определения данных только тогда, когда используется динамический вывод типа. В общем случае он позволяет не указывать тип переменных явно, но выявить все типы на этапе компиляции, и главное, определить все ошибки неправильного использования объектов, связанные с типизацией.
Проблема заключается в том, что автоматический вывод типов на текущий момент не работает.
Например, давайте напишем небольшую функцию на python и вызовем ее с некорректными аргументами:
>>> addlen = lambda l: [e + len(l) for e in l] >>> addlen(['3','4']) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <lambda> TypeError: cannot concatenate 'str' and 'int' objects
Ага! Нам сказали, что нельзя складывать строки с числами и указали конкретное место в коде, где произошла ошибка (ну, если бы это был файл)
И теперь тоже самое на Haskell
Prelude> let addlen l = let x = fromIntegral $ length l in map (+x) l Prelude> addlen ['3', '4'] <interactive>:47:1: No instance for (Num Char) arising from a use of `addlen' Possible fix: add an instance declaration for (Num Char) In the expression: addlen ['3', '4'] In an equation for `it': it = addlen ['3', '4']
ШТА?? Место ошибки не указано, а Possible Fix только сбивает с толку
ApeCoder
27.11.2015 10:11+5Давайте немножко покорректнее,
— статическая типизация сработала и обнаружила ошибку.
— динамическая сработала только тогда, когда вы дошли до выполнения именно этой ветки кода (т.е. динамическая как раз не сработала тогда, когда сработала статическая)
— вы использовали с одной стороны массовый python, с другой — академичный Haskell
— вид исходного кода то же другой.
Я переписал на F# ваш код и получил ошибку на этапе набора кода:
let addLen = fun x -> [for e in x -> e + String.length e]
Можете сами попробовать www.tryfsharp.org/Create
Pilat
27.11.2015 11:41+4Даже хуже — не "— динамическая сработала только тогда, когда вы дошли до выполнения именно этой ветки кода", а «динамическая сработала только тогда, когда вы дошли до выполнения именно этой ветки кода ПРИ КАКИХ-ТО ДОПОЛНИТЕЛЬНЫХ УСЛОВИЯХ»
Lol4t0
27.11.2015 14:48Статическая типизация обнаружила наличие ошибки, а не саму ошибку! Тут большая разница
По языкам претензий вообще не понял, я ж не их долю на рынуке сравнивал
Ну и ссылка у вас не работетApeCoder
27.11.2015 15:061) Как вы понимаете сообщение которое выдал вам Haskell? Что он вам сказал?
2) Разница в целевой аудитории и в проработанности для нее.
3) Что не работает?
ffriend
27.11.2015 02:45+4Я то надеялся увидеть какие-то тренды, наблюдения, а тут… Давайте по порядку.
1. Пример с HTTP запросом на Clojure бредовый. Есть такая штука как REPL, который вроде как в Лиспах изначально и появился, а в Clojure вообще он один из лучших, которые я видел. Так вот в этом REPL можно создать запрос, отправить запрос, посмотреть его поля, вызвать методы, изменить и вообще проверить абсолютно всё, что вообще можно о нём узнать. В общем, аргумент не принят.
2. Про HTML и enlive. Насколько я помню enlive, он работает так: на вход подаётся страничка с шаблоном, над которым работал дизайнер. Эта страница трансформируется в родные структуры данных Clojure и предоставляется набор функций для заполнения отдельных кусков страницы нужными данными. Т.е. родные структуры данных Clojure используются для того, чтобы представить саму страничу. Где здесь DSL? Чем это хуже, чем набор каких-нибудь HTMLNode, которые делают то же самое, но имеют меньше методов для манипулирования? И самое главное, в случае с HTMLNode какой выигрыш дадут языки со статической типизацией? Правильно, никакой, потому что HTMLNode — это по определнию динамическая структура, как и нативные структуры Clojure.
3. Пример с опрелением роутов. Какое отношение плохой API имеет к динамической типизации. Хороший API на Clojure (как и на любом другом статические или динамически типизированном языке) может выглядеть как-то так:
(defroute GET "/" (do-stuff)) (defroute GET "/notes" (do-another-stuff)) (defroute POST "/notes/:id" (do-stuff-with-id))
И примерно так выглядит роутинг в большинстве более или менее популярных Clojure библиотеках. А вот про «прекрасный» роутинг скаловоского Spray почему-то никто не вспоминает. Статические языки гораздо понятней, ага.
4. Что касается частичного введения типизации, то, чёрт возьми, да, это хорошая возможность! Возможность опционально и лаконичным синтаксисом задать дополнительную проверку / документацию в коде — это дополнительная фича, так что почему бы и нет. Проблема в том, что не всё в программировании можно описать статическими типами. Вы не сможете описать HTML страницу строгими типами, вам понадобится обобщённый класс HTMLNode и динамическая проверка содержимого каждого узла. Вы не сможете представять в виде конкретного класса JSON значение — мы можете это отобразить на известный класс, но полностью его представить можно только с помощью обобщённого JSONValue. Вы не сможете реализовать систему акторов со строгой типизацией — скаловская Akka проглатывает любые сообщения и создаёт акторов динамически без проверки типов или даже количества аргументов.
5. Ну, и если всё-таки посмотреть на тренды, то вот список языков, набирающих популярность, из топа гугла (можете предложить другой):
- Erlang — динамический
- Go — статический
- Groovy — динамический
- OCaml — статический (и здесь «статический» значит гораздо больше, чем, скажем, в Scala)
- CoffeeScript — динамичесикй
- Scala — статический
- Dart — статический
- Haskell — статический
- Julia — динамический (с type guards)
4 из 9 — динамические. О да, это конец эпохи.terryP
27.11.2015 12:31+2Groovy — динамический
Смысл языка именно в том чтобы дать возможность писать скрипты/программы с динамической типизацией в Java проектах. На самом деле, учитывая доступ к статическим типам Java это язык скорее с комбинированной типизацией. Как у C# есть dynamic, так у Java есть Groovy.
CoffeeScript — динамичесикй
Это не новый язык, а настройка на JavaScript, то есть его популярность идет от популярности JavaScript.
То есть только 2 из 9 — реально новые динамические языки.deniskreshikhin
27.11.2015 12:58-1>> Это не новый язык, а настройка на JavaScript, то есть его популярность идет от популярности JavaScript.
Тогда и Scala, Groovy это надстройка на JVM. Не будь Java столь популярной вряд ли кто-то стал бы связываться с этими языками.VolCh
27.11.2015 13:06+1И даже Java надстройка над JVM. У JVM какая типизация?
deniskreshikhin
27.11.2015 13:13+1JVM это не язык программирования, у него не может быть типизации)
Но набор инструкций отпимизирован для компиляции статически типизированных языков.
terryP
27.11.2015 13:17+1А JVM надстройка над машинными кодами (у регистров которых, кстати, вполне статическая типизация — флаги, указатели и числовые регистры это разные типы), но вообще говорить о типизации байт кода или машинных кодов весьма мало смысла.
VolCh
27.11.2015 13:28+2У регистров и памяти как раз не статическая типизация, а полная динамическая, то есть отсутствие типизации в принципе. Тип регистра определяется оператором, если можно так выразиться. Оператор арифметического сложения, например, считает что в регистре число, а оператор двоичного умножения — множество битов. Если туда поместить код символа в Юникоде, то никто из них не заметит подвоха.
terryP
27.11.2015 13:39+1Да, но только для регистров общего назначения, однако есть флаговые (Boolean значения), индексные (числовые), указательные и сегментные (хранят указатели), так что давайте согласимся что у регистров комбинированная типизация. :)
ffriend
27.11.2015 13:50+1у регистров комбинированная типизация
Вы уже второй раз используете словосочетание «кобминированная типизация», объясните, пожалуйста, его смысл.terryP
27.11.2015 14:04+1Чистая динамическая типизация — тип определяется только во время выполнения, чисто статическая типизация — тип задается и проверяется на этапе компиляции, комбинированная — тип указывается лишь опционально, часть переменных получаются динамическими, часть статическими.
ffriend
27.11.2015 14:06Тогда Java — это язык с комбинированной типизацией, потому что можно объявить переменную типа Object. Так получается?
terryP
27.11.2015 14:10Нет, Object это статический тип. В Java нельзя просто записать var x, нельзя выйти из системы статических типов, да можно Object привести к любому другому типу, но исключительно в рамках статической типизации, Object тут не отличается от любого другого статического типа, например Number'a. Более того Object вполне реальный класс Java.
ffriend
27.11.2015 14:22В Java нельзя просто записать var x
Object x;
Object привести к любому другому типу, но исключительно в рамках статической типизации
Т.е. без проверки типа на этапе компиляции? Для меня звучит как динамическая типизация.
Более того Object вполне реальный класс Java
В отличие от чего? В Python, например, тоже есть вполне реальный класс object.
Пока что разницу между Java и Python я вижу только в том, что в Java надо писать так:
Foo x = (Foo)y;
а в Python можно просто:
x = y
VolCh
27.11.2015 14:28+2Т.е. без проверки типа на этапе компиляции? Для меня звучит как динамическая типизация.
Вы будете удивлены, но динамичность типизации и тип трансляции никак не связаны. Равно как не связаны динамичность и время проверки типа. Статическая типизация означает лишь, что тип не может измениться после объявления, а всё остальное (компилируемость, проверка попыток изменения во время компиляции и т. д.) к статической типизации не относится, просто статистика создаёт паразитные ассоциации у многих.ffriend
27.11.2015 15:14Из Википедии:
Static type-checking is the process of verifying the type safety of a program based on analysis of a program's text (source code).
…
Dynamic type-checking is the process of verifying the type safety of a program at runtime.
Терминология тут очень скользкая, равно как и в самом процессе компиляции и рантайма (например, JIT-компиляция — это compile time или run time? а интерпретаторы, которые компилируют и выполняют код по одному выражению?). Я тут отталкиваюсь от основной цели статической типизации (как это подаётся здесь) — раннего выявления ошибок за счёт дополнительных ограничений. И вот тут я не вижу разницы между кодом, который я привёл выше для Julia и таким кодом на Scala (у Java нет REPL для демонстрации, но смысл тот же):
scala> var x: Any = 1 x: Any = 1 scala> x = "hello" x: Any = hello scala> x = List(42, 84, 168) x: Any = List(42, 84, 168)
Уберите из программы на Scala аннотации типов и добавьте приведение к типу перед вызовом метода и получите чисто динамический язык.
Это всё, конечно, искусственные примеры и притянуто за уши, но ведь и изобретать некую магическую «комбинированную» типизацию — это не совсем често.vedenin1980
28.11.2015 04:21+1но ведь и изобретать некую магическую «комбинированную» типизацию — это не совсем честно.
Вы ошибаетесь, никто комбинированную магическую типизацию не изобретал. Давно существуют языки программирования у которых официально есть и динамическая и статическая типизация одновременно, например Groovy. Естественно, отдельная переменная в каждый момент времени имеет либо статическую, либо динамическую типизацию. Видимо, такие языки и подразумевались под «комбинированной» типизацией.ffriend
28.11.2015 16:08Так динамическая и статическая типизация, а не какая-то третья «комбинированная». Просто не надо подменять понятия для докозательства своей точки зрения. Динамическая типизация востребована, как судя по статье, которую я привёл, так и по статистике GitHub и, я уверен, по куче других параметров.
VolCh
27.11.2015 14:13+2чисто статическая типизация — тип задается и проверяется на этапе компиляции
В общем случае время задания типа сущности не имеет значения для определения типа типизации. Главное отличие статической от динамической: в первой тип не может меняться в ходе выполнения, а во второй — может. Просто традиционно статически типизируемые языки компилируемые и проверки типа осуществляются на этапе компиляции, но вообще язык может быть статически типизируемый, но интерпретируемый, может быть статически типизированный и компилированный, но проверка типа выполняться в рантайме
VolCh
27.11.2015 13:57+1Хранят они то, что загружено в них явно или нет. Просто некоторые команды считают, что это не просто набор битов, а набор, имеющий четко определенную семантику, то есть тип. Это они так считают, но «транслятор» не гарантирует, что, например, в сегментном регистре хранится адрес регистра.
terryP
27.11.2015 14:07Ну так в C и С++ тоже никто не гарантирует что программист в массив чисел не запишет строку или не запишет как int в double переменную. В большинстве статических языков «транслятор» или компилятор можно вольно или невольно обмануть.
VolCh
27.11.2015 14:19+2Система типов гарантирует :) Или язык неявно, или программист явно приведут строку к массиву чисел, а int к double. И для того, чтобы использовать их как строки или int нужно будет выполнить обратное приведение. И не путайте прямое изменение памяти и доступ к ней через переменную.
terryP
27.11.2015 13:09Возможно, хотя Scala скорее уже позиционируется как замена Java, в то время как CoffeeScript более похож на Project Lombok для Java. Надо просто понимать что у JavaScript нет альтернативы и CoffeeScript лишь обетка над синтаксисом JavaScript, как Java лямбды и Stream Api обертка над обычными методами Java 1-7, с таким же успехом синтаксис CoffeeScript мог стать частью синтаксиса JavaScript, если бы стандарт JavaScript не было так сложно менять.
P.S. Кстати у Julia — комбинированная типизация (опциональные аннотации типов и т.п.), то есть из чисто динамических универсальных новых популярных языков остается один Erlang.deniskreshikhin
27.11.2015 13:37-1Дело в том что в интерпретируемых языках нет компиляции, поэтому «обертка» это единственная возможность технически создать другой язык на той же платформе. По синтаксису же CoffeeScript больше похож на Ruby, чем на Javascript.
И по отношению популярности, Javascript занимает 1 место по количеству репозиториев, а CoffeeScript 16-е. Аналогично Java 2-е, а Scala 19-е и Groovy 26-е.bromzh
27.11.2015 14:40Дело в том что в интерпретируемых языках нет компиляции
Почти все интерпретируемые языки компилятся в байткод. Например, при импорте питоновского файла рядом (или в папке __cache__) будет лежать скомпилированные файлы.
Плюс, у многих языков есть JIT.deniskreshikhin
27.11.2015 15:19Да, верно, но обычно этот байт-код непереносим, в отличии, например, от байт-кода JVM. Т.к. у интерпретируемых языков он играет несколько другую роль, чем у компилируемых.
В общем-то сейчас между интерпретируемыми языками и компилируемыми нет той разницы, что была раньше.
ffriend
27.11.2015 14:02+1P.S. Кстати у Julia — комбинированная типизация (опциональные аннотации типов и т.п.), то есть из чисто динамических универсальных новых популярных языков остается один Erlang.
julia> x = 1 1 julia> x = "hello" "hello" julia> x = [42, 84, 168] 3-element Array{Int64,1}: 42 84 168
В Julia динамическая типизация. Аннотации типов нужны для специализации при компиляции и полиморфизма. Т.е. вы можете объявить метод «add(a::Int, b::Int)» и метод «add(b::String, b::String)», и конкретный метод будет определён динамически в зависимости от переданных аргументов во время первого вызова. При этом до момента первого вызова компилятор / рантайм даже не почешется проверить типы:
julia> foo(x::Int) = 1 foo (generic function with 1 method) julia> bar() = foo("some string") bar (generic function with 1 method) julia> bar() ERROR: MethodError: `foo` has no method matching foo(::ASCIIString) in bar at none:1
VolCh
27.11.2015 14:03CoffeeScript лишь обетка над синтаксисом JavaScript
Все языки лишь обертки над машинными кодами. Да и сам язык машинных кодов в современных процессорах вроде как лишь обертка над аппаратными микрокомандами.
А так, все относительно высокоуровневые языки транслируются в другие языки, зачастую в несколько этапов, например Java транслируется в байт-код JVM, который транслируется в машинный код. А CofeeScript обычно транслируется в JavaScript-код, который транслируется в машинный код. В общем не путайте язык и его реализацию.
VolCh
27.11.2015 13:05+1На самом деле, учитывая доступ к статическим типам Java это язык скорее с комбинированной типизацией.
Что вызов какого-то метода или функции библиотеки гарантированно возвращает конкретный тип не делает язык статически типизируемым. С точки зрения языка это просто такая библиотека. Динамическая типизация означает прежде всего возможность менять тип значений переменных в рантайме. Если она есть — язык динамически типизируемый.
ffriend
27.11.2015 13:47+1Смысл языка именно в том чтобы дать возможность писать скрипты/программы с динамической типизацией в Java проектах. На самом деле, учитывая доступ к статическим типам Java это язык скорее с комбинированной типизацией. Как у C# есть dynamic, так у Java есть Groovy.
Любой динамический язык программирования имеет доступ к информации о точном типе объекта, просто эта информация не закрепляется за переменной во время компиляции.
Это не новый язык, а настройка на JavaScript, то есть его популярность идет от популярности JavaScript.
Ага, а C — это надстройка над языком ассемблера.
Если вы хотите позаниматься софистикой, то можно сказать, что Scala — динамически типизированный язык, потому что в нём есть Any, и Haskell — динамически типизированный, потому что в нём можно не указывать явно типы и т.д. Но давайте всё-таки придерживаться общепринятых определений, хотя бы из той же Википедии.
SerCe
27.11.2015 23:34+1К слову, утверждение Groovy — динамический неверно, Groovy поддерживает как статическую, так и динамическую типизацию, причем позволяет в нужном месте использовать наиболее удобный подход.
ffriend
28.11.2015 01:47Ок, пусть будет так, смысл от этого не меняется — люди любят динамическую типизацию, языки с ней продолжают появляться и развиваться, ни о какой их смерти речи не идёт.
sulnedinfind
27.11.2015 15:20+2Недавно в твиттере на тему понравилось
"Dynamic typing": The belief that you can't explain to a computer why your code works, but you can keep track of it all in your head.
— Chris Martin (@chris__martin) 10 августа 2015
deniskreshikhin
27.11.2015 15:26-8Интересно, если в статически типизированных, компилируемых языках ошибки обычно отлавливают компиляторы. То почему именно в Java, C, C++, C# так популярен дебаггинг и всяческие тулзы для деббагинга?
А вот в Ruby, Python, Javascript вообще деббагинг никто не использует. Все пишут юнит-тесты и этим ограничиваются.lair
27.11.2015 15:53+7в [...] Javascript вообще деббагинг никто не использует
Серьезно?deniskreshikhin
27.11.2015 16:00-3Да, это я погорячился. В браузерах используют, на Node.js редко кто прикручивает.
lair
27.11.2015 16:01+7Угу, следующий вопрос: насколько популярен дебаггинг у тех программистов «статически-типизированных языков», которые активно используют юнит-тесты?
deniskreshikhin
27.11.2015 16:21Так в статье написано «юнит тесты полезны для тестирования некоторой известной функциональности, а не для определения того, насколько что-то в результатах соответствует вашим явным или неявным ожиданиям.»
Я в общем-то думаю что и нормальные программисты на статически типизированных языках предпочитают использовать грамотно составленные юнит-тесты, а не ковыряться в дебаггере.
Но факт есть факт — в статических и компилируемых языках дебаггингу отдается гораздо больше внимания, чем в динамических и интерпретируемых.lair
27.11.2015 16:23+2Так в статье написано «юнит тесты полезны для тестирования некоторой известной функциональности, а не для определения того, насколько что-то в результатах соответствует вашим явным или неявным ожиданиям.»
Это никак не отвечает на мой вопрос.
в статических и компилируемых языках дебаггингу отдается гораздо больше внимания, чем в динамических и интерпретируемых.
У вас есть конкретная статистика, подтверждающая это утверждение? А с поправкой на количество тестов, покрывающих код?intet
27.11.2015 17:19+1Есть большое подозрение, что типичные написанные на динамических и интерпретируемых языках программы гораздо меньше и проще устроенные. Сравните поиск ошибки в каком-нибудь рендеринг страницы, где число задействованных переменных и методов редко переваливает за два-три десятка и отладку какого-нибудь внутри системного компонента.
lair
27.11.2015 17:24Само по себе это только аргумент за то, чтобы разбивать «системные компоненты» на маленькие области, каждую из которых легко отлаживать…
(а в реальности я сильно больше одного раза сидел над жизненным циклом сравнительно простой вебстраницы с дебаггером, потому что без него фиг поймешь)intet
27.11.2015 17:27Но отладку этих небольших компонентов все равно придется производить ) И дебагер лишь один из способов производить отладку.
deniskreshikhin
27.11.2015 17:48>> Это никак не отвечает на мой вопрос.
Исходный комментарий был к статье, соответственно и ответ в контексте статьи.
>>У вас есть конкретная статистика, подтверждающая это утверждение? А с поправкой на количество тестов, покрывающих код?
Элементарно, составьте SQL запрос на стековерфлоу по тегам 'deugging', в среднем на статически типизированных языках это тема всплывает намного чаще.
Можно было бы и по гитхабы статистику собрать, но я не знаю как отличить java-проект который использует дебаггер, от того который не использует.lair
27.11.2015 17:51Элементарно, составьте SQL запрос на стековерфлоу по тегам 'deugging', в среднем на статически типизированных языках это тема всплывает намного чаще.
У вас есть конкретные цифры?
Ну и да, я еще раз повторюсь: а какое тестовое покрытие в тех проектах, где люди используют дебаггер?deniskreshikhin
27.11.2015 17:53-1Если вас это интересует, проведите исследование. Мне тоже было бы интересно на это посмотреть.
Повторюсь — я высказал мнение в контексте статьи. Статья тоже не блещет цифрами, поэтому не вижу смысла тут приводить цифры.lair
27.11.2015 18:00Если вас это интересует, проведите исследование. Мне тоже было бы интересно на это посмотреть.
Я так и думал, что ваше утверждение ничем не подтверждено. Жаль.
(а в статье про юнит-тесты написана банальность, к вашему утверждению отношения не имеющая)deniskreshikhin
27.11.2015 18:06В статье четкий посыл, что ошибки времени компиляции помогают избежать последующих проблем.
Я указал, на то что если это так, то зачем нужны дебаггеры? Тем не менее все IDE для статических типизированных языков идут как правило с дебаггером из «коробки». А для динамически — atom.io, brackets, komodo и т.д. без дебаггера.
Конечно и ежу понятно, что кто-то юзает дебаггеры и с динамическими языками программирования. Я тоже иногда дебажу браузерный Javascript.
Но исходный комментарий был к статье и являлся гиперболой.
Гипе?рбола (из др.-греч. ???????? «переход; чрезмерность, избыток; преувеличение») — стилистическая фигура явного и намеренного преувеличения, с целью усиления выразительности и подчёркивания сказанной мысли. Например: «я говорил это тысячу раз» или «нам еды на полгода хватит».
lair
27.11.2015 18:11-1В статье четкий посыл, что ошибки времени компиляции помогают избежать последующих проблем.
И это действительно так.
Я указал, на то что если это так, то зачем нужны дебаггеры?
Затем, что не все ошибки отлавливаются на этапе компиляции.
А для динамически — atom.io
atom.io — это не IDE, а редактор. Еще он работает со статически типизированными языками. Еще в нем есть дебаггер для node.js (не из коробки, но это значит, что кому-то было настолько надо, что он пошел и написал пакет).
Так что мне все еще интересно услышать основания для вашего утверждения (извините, я его перефразирую) «программы на статически-типизированных языках отлаживают дебаггером, а для динамически-типизированных — только пишут юнит-тесты».
intet
27.11.2015 18:17+2Неправда в самой популярной ide «Блокнот» дебагера по умолчанию нет.
Типичные используемые для статических типизированных языков ide уже довольно зрелые продукты с большой функциональностью.
Всякие же небольшие редакторы Javascript полагают, что в случае необходимости отладку будут производить в браузере, а не в ide.
intet
27.11.2015 18:00+1Элементарно, составьте SQL запрос на стековерфлоу по тегам 'deugging'
Есть небольшое подозрение, что занимающиеся дебагом кода использовали дебагер, а не отлаживались с помощью print или allert.
terryP
27.11.2015 17:13В браузерах используют, на Node.js редко кто прикручивает.
1) Потому что надо прикручивать, до сих пор далеко не каждая IDE может дебажить Node.js, поэтому «вообще деббагинг никто не использует» не от хорошей жизни и от любви к unit тестам,
2) Вы проводили статистическое исследование про «редко кто»? До сих пор главным преимуществом WebStorm перед другими IDE считается мощный дебагер,
3) Средний размер приложений и их сложность в Java, С++, C# несколько больше чем в Javascript,
michael_vostrikov
27.11.2015 19:16Полагаю, что не в последнюю очередь это связано с тем, что в таких языках посмотреть, что представляет из себя переменная, можно только в отладчике. В динамических для этого есть всякие var_dump() и console.log(). С другой стороны, я часто использую отладчик в PHP, он хорошо помогает разобраться в работе плохо написанного кода или в дебрях абстракций и DI-контейнеров.
terryP
27.11.2015 19:36+1в таких языках посмотреть, что представляет из себя переменная, можно только в отладчике. В динамических для этого есть всякие var_dump() и console.log()
Эээ, вы правда верите, что в статических языках (Java, C#, C++) нет аналогов var_dump и console.log (другими словами логирования в принципе или возможности вывести тип переменной в лог)?terryP
27.11.2015 19:52На всякий случай, чтобы в Java получить аналог var_dump и console.log достаточно использовать код log.debug(«переменная» + variable), где log — переменная логера.
michael_vostrikov
27.11.2015 21:44+1Я правильно понимаю, что у variable в этом случае вызывается что-то типа toString(), который мы сами должны реализовать? Я имел в виду не невозможность логирования в принципе и не определение типа переменной, а сложность получить все поля с их типами и значениями для некоторой сложной структуры.
terryP
27.11.2015 22:29+1сложность получить все поля с их типами и значениями для некоторой сложной структуры.
Любая Java IDE позволяет автоматически сгенерить toString() для любого класса за секунду. Есть способы автоматически генерить toString для всех классов проекта. Все коллекции и важные библиотечные классы уже имеют переопределенный toString. В целом, это не проблема от слова совсем.michael_vostrikov
27.11.2015 22:50+2Тем не менее, проще не менять исходники и посмотреть в отладчике. Речь была не только про Java, а про языки в целом.
intet
27.11.2015 20:07Проблема скорее в том, что мало добавить строку записи в лог. Необходимо еще и пересобрать проект, а это не быстро занятие.
terryP
27.11.2015 20:21+1Во-первых, есть такая вещь как hot deploy (подкладывания одного класса на сервер), во-вторых, далеко не всегда пересборка проекта занимает долгое время и далеко не всегда пересборка проекта целиком вообще нужна (так как есть кеши уже скомпилированных классов), в-третьих, в ряде случаев, как например в GWT, можно проверить работу класса/виджета без запуска остального кода.
intet
27.11.2015 20:27C++ hot deploy? Разве подобное существует?
Да и даже если использовать hot deploy на java — мороки куча. Промахнулся со строчкой — добавляй код и заново вызывай метод. Лучше уж использовать специально предназначенный инструмент.terryP
27.11.2015 22:33+1C++ hot deploy
В C++ нет, но вы не только про C++ говорили.
hot deploy на java — мороки куча. Промахнулся со строчкой — добавляй код и заново вызывай мето
Эээ, что за морока, почему я никогда с ней не встречался за 10 лет в Java? И что значит промахнулся со строчкой?
Лучше уж использовать специально предназначенный инструмент.
Я и имел в виду hot deploy со специально предназначенными для этого инструментами…intet
28.11.2015 08:41И что значит промахнулся со строчкой?
Например вы ожидали, что ошибка будет в этом месте, добавили вывод, посмотрели результат, а там все нормально и на самом деле ошибка скрывается где-то ниже по коду. Придется двигаться дальше и смотреть.
Я и имел в виду hot deploy со специально предназначенными для этого инструментами…
Специально преднозначеный инструмент это дебагер. Он позволяет не только посмотреть содержимое переменных. Но и узнать стек вызова, поменять прямо в рантайме значение некоторых переменных, выполнить произвольный код, заморозить на время процесс и еще многое другое. При отладке через print все это делать гораздо неудобней.
Borz
27.11.2015 22:47+1Любая Java IDE позволяет автоматически сгенерить toString() для любого класса за секунду
AspectJ вам в помощь — не только пересобирать, но и сам класс менять не придётся
deniskreshikhin
27.11.2015 20:24Не знаю почему вас минусуют, но действительно есть такой нюанс.
Что бы получить нормальный дамп требуется глубокая интроспекция, а в некоторых компилируемых языках этого порой трудно добиться.terryP
27.11.2015 23:26+3потому что
1) язык со статической типизацией != компилируемый,
2) из списка C,C++,C#,Java — лишь половина компилируемые языки, ни С#, ни Java — не являются компилируемыми языками, это одновременно и интерпретируемые и компилируемые языки, а которых возможна и кодогенерация на лету и evel и работа в интерпретируемым режиме,
3) никакой проблемы в получении нормальный дамп нет ни в C#, ни в Java достаточно определить toString любым из ручных и автоматических способов, насчет С++ — не скажу,
4) полный аналог js «console.log(JSON.stringify(object))» на java записывается как «log.debug(gson.toJson(object))», на C# скорее всего не сложнее,
Вывод: утверждение «в таких языках посмотреть, что представляет из себя переменная, можно только в отладчике» совершенно неверно.
zahardzhan
28.11.2015 01:38-3В ближайшие лет 10 актуальными станут типы данных вроде «подозрительный человек в маске с автоматом в левой руке и бананом в правой», или допустим «крутой поворот за угол слева от фонаря». Я с трудом себе представляю как с этим будут справляться языки с самыми современными системами статической типизации, и будет ли статическая типизация вообще актуальна для описания таких вещей.
zahardzhan
28.11.2015 01:50-2Исходя из лично моего опыта могу сказать, что статическая типизация была практически бесполезна чтобы гарантировать правильную работу моих программ. Потому что статической типизацией почти невозможно отловить ошибки вызванные неправильной последовательностью передачи данных между десятком параллельно работающих модулей. Для этого наверное нужны какие-то новые математические теории типизации, основанные скажем на темпоральной логике или чем-то подобном.
vintage
28.11.2015 08:18Если вы про различные виды гонок, то тут проблема не в типизации, а в концепции разделяемых данных. Современные языки уже не разделяют память между потоками по умолчанию, вынуждая программистов строить межпоточное взаимодействие по протоколам, в реализации которых статическая типизация очень сильно помогает.
zahardzhan
29.11.2015 02:35-6Ребятки, я понимаю что вы меня минусуете на хабре когда я веду себя довольно аггресивно по отношению к мнению других людей, возможно не обладая достаточной квалификацией для того чтобы вести с ними аргументированный спор. Но когда я говорю о собственном опыте в разработке ПО и вы ебошите минуса на мои комменты мне просто хочется чтобы вы пошли нахуй. Довольно адекватная и честная реакция, вы не находите? Спасибо за внимание.
zahardzhan
29.11.2015 02:45-4Вы просто не забывайте, что вы оставляете свои минуса к комментариям под сверх-толстой статьей о «смерти» динамических языков в которой автор просто точно так же как и я делится с такими же как он своим опытом создания ПО.
vintage
29.11.2015 10:28+7Это так мило, когда пользователь с кармом 23 жалуется пользователю с кармой -15 на пару минусов в комментариях :-) да вы зажрались, батенька!
zahardzhan
29.11.2015 16:27-2Я честно говоря вообще не понимаю значение этих цифр. Есть какая-нибудь таблица на этом ресурсе, в которой указаны диапазоны значений «кармы» и что они говорят о пользователе. Что значит -15?
ApeCoder
28.11.2015 14:46+2zahardzhan
29.11.2015 02:32-1Как вы думаете, станут ли они популярными в мейнстриме? Для этого надо будет написать докторскую по компьютерным наукам или с этим справится обычный школьник?
develop7
29.11.2015 09:59+2не надо говорить так, будто между щкольниками и докторами наук никого нет. этот приём называется «ложная дихотомия» и является демагогией.
ApeCoder
30.11.2015 09:47+1Да фиг знает. Мне кажется, всякие code contracts приближают к этому. Еще вспомните что linq это монада, только Эрик Маер все переименовал для понятности массовыми кодерами.
Т.к. я на самом деле на знаю всех тонкостей ЗТ, то не могу сказать с уверенностью, но мне кажется, что результат будет примерно такой, что программер будет на доступном ему уровне чуть побольше описывать про типы данных, а компайлер будет на это интеллектуально ругаться.
arielf
28.11.2015 22:52+1Увы, мне лень читать все комментарии, и я не знаю, говорили уже или нет аналогичное моему мнению, но лично меня ужасно раздражают статьи с жёлтыми сенсационными заголовками. 'Конец эпохи динамических языков', 'Электронная почта давно мертва', 'Закат языка СИ' (ага, уже как сорок лет с лишним) и прочие. Прямо названия голливудских триллеров. Неужели их авторы серьёзны? Если так, видимо, они ожидали серебряной пули, а оказалось, что каждый язык имеет свои ограничения и, как следствие, свою нишу. Когда-то многие считали, что телевидение убьёт кинотеатры, но они, как видите, всё ещё живы.
ApeCoder
30.11.2015 09:49+3Мне лень читать комментарии начинающиеся со слов «Мне лень читать комментарии», но меня раздражают комментарии, начинающиеся со слов «Мне лень читать комментарии», потому, что часто основная ценность статьи — это провокация на комментарии.
maxp
30.11.2015 13:19+1Наброс, как наброс.
С идиотскими аргументами типа —
«Посмотрите на этот метод! Пежде чем его использовать придется посмотреть в документацию!»
Напрашивается вопрос — «а при чем тут типизация?».vedenin1980
30.11.2015 13:49+2«а при чем тут типизация?»
Так это известная проблема динамической типизации — IDE не может выдавать подсказки методов классов и типов параметров. Если при статической типизации код часто может быть самодокументированым, типы и названия переменных могут заменять комментарии, то при динамической типизации с этим несколько сложнее.
Все не настолько плохо, как описано в статье, но определенный недостаток динамической типизации тут есть.maxp
30.11.2015 14:13+1Это понятно и с этим никто не спорит.
Но в примере у автора wrap-params, а это такая штука, которая принимает на вход request и на выход отдает тоже request. То есть речь о том, что автор за уши притягивает абсолютно не подходящие аргументы.
Или возьмем такую другой вариант передергивания — «какой DSL лучше всего описывает HTML?»
А кому вообще надо описывать html? Ведь у девелопера обычно совсем другая задача, а именно,
«каким-то образом запихать мою структуру данных в браузер, чтобы он ее понял».
Цепочка выглядит так
mydata -> templater -> html -> http(tcp,gzip,...) -> html -> parser -> dom
или вот так (в случае Clojure)
mydata -> hiccup -> html -> http(tcp,gzip,...) -> html -> parser -> dom
причем, во втором случае hiccup — это по сути тривиальное проеобразование теми же средствами, которые уже работают с mydata безо всяких дополнтельных «языков шаблонов» и т.п.
И ключевые моменты здесь «тривиальное» и «теми же средствами».zahardzhan
01.12.2015 09:23+3Лисперы — это и есть те самые легендарные «программисты на HTML'е», только об этом никто не догадывается, потому что все думают, что скобки-уголки фундаментально отличаются от круглых скобок.
VolCh
01.12.2015 10:15А кому вообще надо описывать html?
Тому, кому нужно создать html (неважно на сервере или на клиенте, хотя чаще, имхо, на сервере), но его синтаксис не нравится. Как вариант — кажется избыточным. Одни закрывающие теги чего стоят.Borz
01.12.2015 10:43вы ошибаетесь в основе — не надо создавать HTML, — надо создавать что-то, что будет видеть или с чем будет взаимодействовать пользователь и HTML просто одна из форм описания этого что-то. Так же, как и CSS и JS
tangro
01.12.2015 11:48И тем ни менее во всём этом прекрасном стеке абстракций всё-таки будет и слой, который из «чего-то» создаёт HTML для браузера и слой в браузере, который читает HTML и превращает во «что-то, что будет видеть пользователь». Обоим этим слоям нужно описывать html.
VolCh
01.12.2015 15:19Я не пишу софт для пользователей, я пишу софт для программных агентов (в том числе пользовательских браузеров) и часто мне нужно отдавать HTML. В лучшем случае можно считать, что я пишу описание DOM на HTML и считаю, что HTML для этого неудобен, в частности избыточен, но другого способа сформировать DOM на клиенте у меня нет.
whitepen
02.12.2015 10:40-2Все вот пишут на коком бы это языке написать, что по меньшей мере глупо. Пишут на библиотеке примитивов, собирают под свои нужды чужие разработки. Ну и какая разница на чем писать? Пишем на С++ быструю библиотеку и подставляем в интерпретатор. С++ можно использовать как будто он С без плюсов, поэтому С ваще не нужен. Но если человек написал на С++ примитивы, то ему просто легче на С++ набросать общую сборку. Но если в компании много девочек, то они хочут простой и понятный язык верхнего уровня, и все то им не нравится. C# хорош своими проработанными библиотеками NET, и нет особого смысла дописывать на C++. Другое дело что на 90% компьютер используется для игр где нет всех этих интерпретаторов, только хардкор. На 1% компьютер используется для научно-технических целей. Речь идет о 9% ПО для web служб, где нужно быстро и красиво, тут нужны пусть и не эффективные, но не глючные средства. Есть еще одна ниша — обработка данных. Тут свои заморочки — массив данных нужно обработать ровно один раз, а потом программу выбросить. Ну и как быть уверенным, что данные обработаны правильно? А никак. Ну давайте придумает такой язык, где все будет сразу и без ошибок — не получится. Обработка больших данных — это тот самый 1%.
sergeylanz
Согласен полностью. Динамические языки это классно когда маленькие вещи пишешь а потом просто АД. Согласен что не просто так много новых языков они статические потому что есть спрос и понимание. Я нашол себе GO для этих вещей. Стаческий, быстры и код одинако похож у разных людей.