Ну и будет как с JSON — лесенка скобочек в середине файла и фиг поймёшь какая к какому блоку относится :-)
JSON не позволяет, к сожалению, ставить комментарии. Но само наличие {} скобок позволяет использовать '%' для перехода с начала на конец блока и обратно — это очень помогает в подобных случаях.
Для аналогичной функции в indent-based синтаксисе есть, например, vim-indentwise. Но с ним, если не ставить эти #end, а есть несколько вложенных блоков, после перехода на конец блока нельзя вернуться на начало блока, возврат будет неоднозначен.
А когда начало блока сверху за границей экрана, а конец снизу за границей, то вообще не понятно с чем ты работаешь.
Да, внутри блока это может быть сложно понять. В некоторых IDE (навскидку не помню) я видел показ этой позиции в заголовке окна (не так, как ниже на скриншоте, а в виде пути пакет-класс-функция).
Я считаю это бесполезным, чуть менее, чем полностью.
Почему бесполезно?
Оно подсказывает, какие слова тут можно подставить, и умеет их дополнять. Также — показывает имена и типы аргументов, по которым можно понять, что и как задавать.
Оно может анализировать код и показывать ошибочные (например, опечатка в имени метода) или подозрительные конструкции.
Я не знаю, может, Вы имеете в виду какую-то злобную специфику JS. Я с ним слишком мало работал, чтобы знать такие тонкости. Но для Питона и для >95% случаев меня функциональность таких средств устраивает; когда она не работает — уже описывал раньше — когда я сам как автор кода вмешиваюсь в логику и делаю её изменчивой в рантайме.
Кроме того, чтобы все эти тайпчекеры работали, вам так и так придётся отказаться от динамических возможностей языка.
В большинстве случаев — да. Я не могу, например, писать
if isinstance(max_time, str):
max_time = int(max_time)
да и безусловную конверсию лучше не применять (хотя если средство хоть чем-то умнее крышки дубового стола, оно будет работать в логике, аналогичной Static Single Assignment).
Или, я не смогу использовать присвоение некоторой переменной одного из трёх значений True, False или None (у меня это долго был любимый приём) — оно выведет тип для этого значения, только если придумает внутри себя enum, а так как все три в Питоне это синглтоны двух разных типов, ему будет сложновато это сделать.
Но таких случаев в реальной практике оказалось ой немного. А вот простые варианты типа "в этой переменной всегда целое" оно вывело бы на ура. Точно так же как упомянутая рядом схема Хиндли-Милнера работает в ML и семействе.
Он не может "не суметь".
См. выше пример с конверсией в целое на ходу. Вывод типа "местами снег, местами град, местами variant — или int, или str" и есть то "не шмогла".
Ну и какая программисту разница, если всё работает корректно?
Разница в том, что мимикрия подобного рода означает слишком динамическую типизацию. И в Питоне, и в JS это поиск по названию метода в цепочках словарей методов суперклассов (прототипов).
Если мы вводим синтаксическую поддержку статической типизации, её лучше иметь в вариантах указания "тут постарайся оптимизировать" или "тут ты обязан вывести конкретный целочисленный тип, а его размер, так уж и быть, я тебе доверю выбирать".
Мнэээ… перед этим шла речь о непереносимых выражениях, например. Если кто-то напишет x<<200, где x типа int, что это, как не проблема семантики (содержания, смысла… проигнорируем сверхтонкие отличия)? Ну или пора вводить новую систему терминов.
Что за arguments тут — я не понял. Имеется в виду массив всех аргументов функции в JS? Вот тут, да, это настолько специфично для языка, что ближе к синтаксису, чем к семантике. Но я таки предпочёл бы тут видеть какое-то новое слово...
Я таки слишком неясно выразился. Имелось в виду, что программист ошибётся в какой-то мелочи, и результатом станет не тот тип, который ожидался. Явное называние ожидаемого типа в этом случае является дополнительной защитой со стороны программиста против собственных ошибок. (Ну и против ошибок компилятора, которые всегда есть, но вероятность в каждом конкретном случае ниже на несколько порядков...)
Типы (топ-левел-функций) лучше указывать потому, что типы — это документация, да и явное указание типов позволяет сделать более читабельные сообщения об ошибках типизации (вы накладываете больше констрейнтов, у тайпчекера меньше вариантов).
Да, это тот же самый принцип, что я описал абзацем выше. Но и типы промежуточных переменных — это такая же документация и такая же помощь тайпчекеру.
Я видел последующую дискуссию про то, что такое линтер. Я тут имел в виду статический анализатор любого вида, который способен опознать ситуации типа неиспользованных переменных, типичных опечаток и тому подобного. Такие средства вполне способны проанализировать код и найти, например, неверно передаваемый параметр. С Питоном у меня это реально работало.
Если же речь не о линтерах, а тайпчекерах, то для них опять же нужны тайпхинты,
Если такое средство видит, что параметр используется как индекс в списке, оно предположит, что тут должно быть целое. Если его индексируют строкой — оно должно быть совместимым со словарём. И так далее. Явные хинты тут не обязательны. Хотя, да, желательны — я бы предпочёл их видеть много где — но в последних версиях Питона, например, это уже делается.
О чём опять же программисту сообщается сразу, а не когда в рантайме прилетит что-то не то.
Это если он не сумел вывести тип. А если вывел, но не тот, что думал программист?
Как правило программисту всё-равно какой там тип, лишь бы крякал как утка.
Так в том и дело, что он хочет утку, а получается вдруг амадина. Она тоже крякает, но как-то невыразительно :)
Тут уже привели минимум один пример: если из возраста 100 вычесть возраст 120 (оба валидны), получится отрицательное число, которое возрастом не является.
Но сравнению это не мешает: если возраст это не отдельный тип со своими операциями, а уточнение Integer, то сравнение выполняется по правилам Integer, и выход разности за допустимые пределы игнорируется. Тем более что сравнение может вылиться, например, в инструкцию SLT процессора стиля MIPS/Risc-V, которая вообще формально ничего не вычитает :)
А почему собственно 120? Почему это вообще константы? Может ли этот тип быть параметризован другим типом, задающим границы?
Есть понятие настраиваемых пакетов, которое практически точно соответствует шаблонным классам C++. Собственно, range задаёт такой же настраиваемый пакет, но встроенного определения.
Что будет, если в операции участвуют Int и Range одновременно?
В общем случае операции над range-типами исполняются так же, как над их базовым типом, а проверка на диапазон производится уже при присвоении целевой переменной (или можно форсировать конверсией к её типу какой-то части выражения). То есть, пока вы результат никуда не присвоили или не проконвертировали, сумма Integer + range 1..120 будет считаться так же, как сумма двух Integer. Если это 32-битные, то переполнение будет диагностировано по выходу любого промежуточного результата за общеизвестные -2147483648...2147483647. Более узкий диапазон, как уже сказал, будет проверяться, если вы присвоите переменной типа Age или напишете что-то в стиле Age(A1+X) как одну из компонент более сложного выражения.
Режим с игнорированием всех переполнений (аналогично unsigned в современном C, всей числовой арифметике на стандартных операциях в Java...) возможен с использованием определений типа mod, например mod 2**32 значит 32-битное беззнаковое с заворотом результата (обрезанием до 32 бит). Если нужно считать таким образом, требуется явная конверсия в такой модулярный тип и обратно. Модулярные — только целые без знака.
Резюмируя, всё это в языке хорошо определено (иначе бы его не приняли для DoD:)), и достаточно оптимально, как для задачи "добиться отсутствия неожиданных эффектов чуть менее, чем везде". Так что Ваши вопросы по системе типов всего лишь требуют внимательного похода в место типа такого.
В Ada результатом арифметических операций над некоторым типом является «базовый» тип этого типа (для определения вида range 1..120, насколько я помню, это будет Integer), но при присвоении переменной любого ограниченного типа будет выполнена проверка на вхождение в диапазон этого типа.
Поэтому, например, если мы считаем A1+A2-A3, где все три типа Age, и A1+A2 вылазит за его диапазон, но A1+A2-A3 не вылазит, промежуточная ошибка не будет замечена, но если результат всего выражения будет присвоен AR типа Age и вылезет за 1..120, будет исключение.
К вопросу о корректности разности возрастов это не относится.
Чтобы сделать, что разность возрастов была отдельным типом, нужно создать «пакет» в терминах Ada (это лучше всего соответствует «классу» в C++ и аналогах) и для него уже определить function "-". Механизм, таким образом, для этого есть, хоть и громоздкий. Там уже можно определить и все необходимые прочие операции и ограничения этого типа.
Если исключить возможность явной конверсии своих типов данных в стандартные (как целые), то можно обеспечить и типобезопасность для контроля размерностей (например, не присваивать километры миллиграммам). В смысле этих возможностей Ada не уступает какому-нибудь C++, хотя и выражает свои возможности более громоздко.
> При наличие отступов, фигурные скобки не несут никакого дополнительного смысла.
Есть один случай, когда отсутствие скобок таки вредно влияет на восприятие: это поиск конца блока, не охватываемого одним взглядом, а особенно, когда таких блоков несколько. Что-то вида
if условие1:
...
if условие2:
... тут пара экранов всякого ...
#end
#end
Вот без этих #end увидеть конец блока может быть очень нетривиально.
Кто-то скажет, что нефиг такие крупные блоки создавать. Я не буду сильно возражать, но случай разный бывает, и код — тоже. (Особенно при отсутствии оптимизатора, который позволил бы вынести действия в отдельную внешне видимую функцию и потом успешно её заинлайнить.)
А почему Вы решили, что 120.0 будет обязательно double? Может, это single («float» в C и потомках). А может, вообще должно быть тут десятично точное значение и применена десятичная арифметика, чтобы никакие 1.1 не надо было округлять в мелких знаках.
120 — почему integer, а не short или long? И даже почему не double?
На примере констант это всё не видно, но как только начинаются реальные операции с данными, тип вдруг начинает иметь значение.
> на надо гуглить какое там апи у такого-то модуля — среда разработки сама адекватно всё подскажет.
IDE с поддержкой динамически типизированных языков уже существуют и активно развиваются.
> не надо запускать и дебажить для того, чтобы обнаружить глупую опечатку — компилятор и даже среда разработки понимают, что ты написал, и как можно раньше рассказывают тебе о проблемах.
Аналогично, для динамически типизированных языков есть средства линтинга, и профессионалы не выпускают код без него. Для Python я с ходу могу назвать 4 таких средства, для Javascript — 2, для многих других есть встроенные анализаторы (например, в Perl исключение по use strict генерируется именно таким контролем, а не ошибкой собственно рантайма типов).
Но есть таки принципиальная разница. Грубо говоря, для статически типизированных языков то, что вычисляется при компиляции, будет аксиомой при выполнении (объект типа X => у его методов будет именно та сигнатура, что у класса X). Для динамически типизированных это не обязательно — Python, Javascript позволяют подменять подложку реализации на ходу. Линтинг в принципе не способен отловить такие ситуации. Их запрет для возможности статического анализа — уже вопрос административной политики при разработке, а не собственно языка.
> не надо постоянно вручную проверять и преобразовывать типы, на случай, если на вход передадут какую-то дичь. Типичный пример: String( arg ).toLowerCase()
Да, фактор существенный. Мне в этом смысле понравились правила от Hola для Javascript: например, если требуется, чтобы на входе функции было число, пишется +x, а если строка — ""+x. Рантайм умеет опознавать эти ситуации и переводить их в конверсию типа.
Но вот lowercase это уже за пределами обычной возможности системы типов, надо делать свой враппер.
> типы вручную пишутся только там, где пользователю это важно — в остальных местах они выводятся автоматически
Пока что это достаточно малая часть таких языков. И даже в них автоматический вывод работает не всегда и не везде адекватно. А главное — что для уверенности программиста, что тип получился именно нужный, приходится его указывать явно.
Строгость типизации — понятие достаточно относительное. По сравнению с JavaScript, где преобразование между числом и строкой — норма, C++ строго типизирован, а по сравнению с Go, где даже если int 32-битный, то преобразовывать между int и int32 можно только явно — нестрого типизирован.
Поэтому, при рассмотрении строгости типизации лучше вводить несколько канонических уровней и сравнивать с ними:
1. Вообще никаких неявных конверсий (Go, или близко к нему).
2. Конверсии по умолчанию — между числами, или только явно разрешённые функции, или только в особых контекстах (основной компилируемый майнстрим, C++, C#, Java и т.п.)
3. Размыта граница между строкой и тем, что она представляет (JavaScript, Perl...)
4. Вообще всё есть строка, оптимизации есть, но это принципиально не отменяют (sh, Tcl...)
> Все, кроме гитоводов, привыкли к тому, что ветка растет от ствола или ветки потолще и включает все свои коммиты.
> И только в гите ветка — это именованная голова, а принадлежность коммитов определяется только по связям и работает лишь до первого мержа.
Этого я не понял, учитывая, что безымянные головы вроде есть только в Hg, а для остальных различие между этими интерпретациями несущественно.
> Отсоединеннный коммит — это вообще уникальная гитофича, похожая до степени смешения на багу.
Оно существует только при особых режимах работы. Меня вот множественные головы напрягают больше :)
> Необходимость помнить, запушен ли коммит, после мерка вымораживает.
Этого не понял совсем. Сравнение с Hg или CVS/SVN? В любой распределённой системе локальный коммит может быть не запушен.
> Любовь к rebase есть любовь к фальсификации истории в угоду чисто эстетического профита от няшной линеечки коммитов.
«Эстетический профит» превращается в реальную потребность понимать, что происходило, при разборе через 1-2-5-10 лет, когда никто не помнит подробностей происходящего. И главное не линейка, а чёткость и законченность каждого отдельного действия, а также разделение действий по характеру (как минимум, ни в коем случае не смешивать форматирование, рефакторинг и изменение функциональности). Линейчатость удобна, но не критична.
> но посмотрев и сравив, выбрали и мерк и не пожалели ни разу.
Ну так не CVS же выбрали (имел я дело с одной конторой в 2015-м, принципиально сидящей на CVS — это был ужас). Но стоит таки оценить, как это повлияло на методы работы с кодом и с задачами.
> Фальсификация истории системой контроля версий с внесением ошибок туда, где их не было — это «вообще ни о чем»? Ну-ну.
Да, именно. Если убрать эмоции и само слово «фальсификация», которое уже содержит в себе эмоциональную оценку, то надо начать с признания, что истории, если она не зафиксирована в некоем авторитетном хранилище, вообще нет. Даже без SCM на локальных файлах можно написать любую историю, а с distributed SCM любого вида это становится тривиально — всегда есть возможность взять другую ветку или рабочую копию и повторить начисто. Поэтому вопрос должен стоять так — чего мы хотим от истории? Видеть коммит промежуточного кривого состояния каждый раз, когда программист отшёл в туалет, или всё-таки набор логически цельных изменений? Не знаю, как у вас, а вокруг меня второе таки преобладает.
> Это еще почему? Вы после слияния конфликты не правите, тесты не прогоняете, упавшие сразу не чините?
> Полностью автоматического слияния в общем случае не бывает, ручные правки являются частью процесса.
Именно потому, что ручные правки при merge предполагают правки именно конфликта, а не произвольного места.
Если у вас в foo.h поменялось getFoo() на getFoo(FooContext*), это не повлияет на неправильное использование уже несуществующего getFoo() в bar.c.
Чаще (по тому, что я видел) делают таки правки после мержа, чтобы разделять логически разные действия в разные диффы. Но это требует возможности отменить тестирование самого merge commit.
> Вот только если вы те же самые коммиты склеите ребейзом — вы получите целую цепочку нерабочих коммитов и заведомо неверный результат поиска места ошибки, если она всплывет не сразу.
Именно поэтому я и рекомендовал при таком ребейзе надо править нерабочие коммиты, а не делать правку им вдогон.
Это не настояние на ребейзе вместо мержа или наоборот. Это именно как сделать такой ребейз беспроблемным для будущего поиска.
> У гита есть аналог cherry pick но он ссылку на оригинальный коммит добавляет только опциально и только в комментарий.
Ну я её добавляю всегда. Автоматизация показа этой связи, возможно, полезна, но пока что не сталкивался с такой необходимостью.
> И какой смысл рассказывать про чудо-индекс, если хороший тон — это маленькие коммиты, а mq в мерке кроет индекс как бык овцу?
Не кроет он никак индекс, потому что для представления патча для mq он уже должен быть чистым от прочих изменений.
> Так а чем именно? После 3.8 его практически не правили.
Я про «до».
> Ну это, в гите наличие индекса существенно усложняет чуть менее, чем весь интерфейс. А пользы — чуть.
Ну вот тут и видна фатальная разница. Для меня это не «чуть», это критично. И соответственно ценность всех остальных механизмов, если они это не поддерживают, снижается в разы.
> Таким образом, без дополнительных телодвижений новые головы просто-напросто не выпинываются.
OK, принято. Я не помню, откуда взял про передачу всех голов — скорее всего, это утверждал один из апологетов Hg, а я не проверил. Осталось не разрешать их локально по умолчанию :)
Я не говорил о публично выставленной части (тут скорее надо сослаться на концепцию Phases), хотя и тут есть разные мнения — иначе бы в `git help rebase` не было секции «Recovering from upstream rebase». Но именно свою локальную работу привести в порядок, по-моему, обязательно.
> вероятно, речь об угадывании rename/copy вместо записывания
Тут я тоже удивляюсь, что мешает в Git добавить запись этих данных, если автор явно хочет. Вероятно, они не считают частный случай настолько существенным (над частичным разбросом содержимого в разные файлы), а детект по содержимому сейчас работает достаточно неплохо (и для log, и для merge).
> Не совсем понятно, чем именно вас не устраивает hg histedit
До последних версий (3.8? я сбился с историей нумерации) он был непригоден. Сейчас надо перещупать заново.
> частичный коммит реализован искаробочным плагином record
1) deprecated 2) принципиальная разница в отсутствии явного индекса, я не могу перед коммитом посмотреть, что же получилось. (В описании вообще подтасовка — «interactively select changes to commit» не подразумевает самого коммита, но он происходит.) Да, можно потом корректировать историю, но это много лишних движений. Ещё и легко ошибиться (strip без --keep потеряет изменения).
> hg shelve (правильно, это тоже искаропки)
И снова без возможности учесть индекс. Как-то его, я смотрю, в Hg совсем не оценили, и всё сравнение в итоге утыкается в него.
> Mercurial же просто записывает всё за пользователем.
Я бы оценил эту фичу в варианте 1) автоматически не включена, 2) препятствует внешнему обмену, пока не указана конкретная голова или не осталась одна. Но в текущем варианте это источник тяжёлых диверсий.
> Это в каком виде? В виде, не соответствующем реальности? А зачем? Какой прок от недостоверной истории?
Должна ли история сохранять все опечатки и душевные метания программиста? Я лично живу в мире, где лучше, что автор выставляет некоторое «предложение», и только с момента его принятия оно становится частью истории, независимо от того, какие внутренние процессы привели к нему.
Ссылки на статьи после этого — забавны, но не имеют отношения к текущему обсуждению, и, по-моему, вообще ни о чём. Если требуется рабочее состояние после каждого коммита, то это должна обеспечить автоматизированная проверка. Например, у нас к Gerritʼу для этого был прикручен Jenkins. Если коммиты были отправлены под другую версию, и корректировка под новое API была сделана потом одним рывком, а не на каждый участвующий, то проверка не прошла, и это уже вопрос административный, допускать такое вообще или нет. Но хуже то, что с merge, которое предложено взамен, если это именно merge, то по любому получим неработающую версию после слияния, а если тут же править результат слияния, то это будет уже не честное слияние, а доработка. По-моему, тут идеального выхода нет вообще.
> Оно всего-лишь создаёт скрытую локальную ветку для незаконченной работы. Это даже в SVN не сложно реализовать,
Локальную — в SVN?
> Чувствую у вас не редки ситуации, когда у вас на машине «всё работает», а у того, кто пульнёт ваши изменения, всё сломается, ибо вы то забыли закоммитить, то закоммитили чего лишнего.
Бывают — там, где нет автоматизированной проверки коммита. (Сейчас я расслабился, потому что основную компоненту веду один. Раньше за счёт центрального Gerrit с code review такое не проходило, и приходилось заботиться сразу.) Но доля таких ситуаций всё равно в разы меньше, чем грязи в процессе, когда вначале коммитится со всеми локальными мотлохами, а потом они вычищаются (а часто даже тупо забываются, пока не всплывают на продакшене). Так что чисто статистически от этого тоже польза.
> Я так и не уловил этого тонкого различия. Почему вы противопоставляете часть (голова) целому (ветка)? Ветка метро — это всё множество последовательно соединённых станций, или только лишь конечная станция?
Потому что Hg позволяет быть нескольким безымянным головам одновременно. А это уже в случае метро не одна ветка, а несколько, вплоть до всей сети.
> Ничего удивительного. Git — это этакий прокачанный CVS, который отслеживает не отдельные файлы, а группы файлов, и плевать он хотел на историю их содержимого (которое кочует между файлами).
Я не понял логики этого замечания. CVS следил за каждым файлом отдельно, а Git, оказывается, «прокачанный CVS», но за одним файлом уже не следит.
JSON не позволяет, к сожалению, ставить комментарии. Но само наличие {} скобок позволяет использовать '%' для перехода с начала на конец блока и обратно — это очень помогает в подобных случаях.
Для аналогичной функции в indent-based синтаксисе есть, например, vim-indentwise. Но с ним, если не ставить эти #end, а есть несколько вложенных блоков, после перехода на конец блока нельзя вернуться на начало блока, возврат будет неоднозначен.
Да, внутри блока это может быть сложно понять. В некоторых IDE (навскидку не помню) я видел показ этой позиции в заголовке окна (не так, как ниже на скриншоте, а в виде пути пакет-класс-функция).
Почему бесполезно?
Оно подсказывает, какие слова тут можно подставить, и умеет их дополнять. Также — показывает имена и типы аргументов, по которым можно понять, что и как задавать.
Оно может анализировать код и показывать ошибочные (например, опечатка в имени метода) или подозрительные конструкции.
Я не знаю, может, Вы имеете в виду какую-то злобную специфику JS. Я с ним слишком мало работал, чтобы знать такие тонкости. Но для Питона и для >95% случаев меня функциональность таких средств устраивает; когда она не работает — уже описывал раньше — когда я сам как автор кода вмешиваюсь в логику и делаю её изменчивой в рантайме.
В большинстве случаев — да. Я не могу, например, писать
да и безусловную конверсию лучше не применять (хотя если средство хоть чем-то умнее крышки дубового стола, оно будет работать в логике, аналогичной Static Single Assignment).
Или, я не смогу использовать присвоение некоторой переменной одного из трёх значений True, False или None (у меня это долго был любимый приём) — оно выведет тип для этого значения, только если придумает внутри себя enum, а так как все три в Питоне это синглтоны двух разных типов, ему будет сложновато это сделать.
Но таких случаев в реальной практике оказалось ой немного. А вот простые варианты типа "в этой переменной всегда целое" оно вывело бы на ура. Точно так же как упомянутая рядом схема Хиндли-Милнера работает в ML и семействе.
См. выше пример с конверсией в целое на ходу. Вывод типа "местами снег, местами град, местами variant — или int, или str" и есть то "не шмогла".
Разница в том, что мимикрия подобного рода означает слишком динамическую типизацию. И в Питоне, и в JS это поиск по названию метода в цепочках словарей методов суперклассов (прототипов).
Если мы вводим синтаксическую поддержку статической типизации, её лучше иметь в вариантах указания "тут постарайся оптимизировать" или "тут ты обязан вывести конкретный целочисленный тип, а его размер, так уж и быть, я тебе доверю выбирать".
Мнэээ… перед этим шла речь о непереносимых выражениях, например. Если кто-то напишет x<<200, где x типа int, что это, как не проблема семантики (содержания, смысла… проигнорируем сверхтонкие отличия)? Ну или пора вводить новую систему терминов.
Что за arguments тут — я не понял. Имеется в виду массив всех аргументов функции в JS? Вот тут, да, это настолько специфично для языка, что ближе к синтаксису, чем к семантике. Но я таки предпочёл бы тут видеть какое-то новое слово...
Я таки слишком неясно выразился. Имелось в виду, что программист ошибётся в какой-то мелочи, и результатом станет не тот тип, который ожидался. Явное называние ожидаемого типа в этом случае является дополнительной защитой со стороны программиста против собственных ошибок. (Ну и против ошибок компилятора, которые всегда есть, но вероятность в каждом конкретном случае ниже на несколько порядков...)
Да, это тот же самый принцип, что я описал абзацем выше. Но и типы промежуточных переменных — это такая же документация и такая же помощь тайпчекеру.
А что же это, если не семантика? В понятие синтаксиса уже не влазит.
Ну да. А вы считаете это недостаточным?
Я видел последующую дискуссию про то, что такое линтер. Я тут имел в виду статический анализатор любого вида, который способен опознать ситуации типа неиспользованных переменных, типичных опечаток и тому подобного. Такие средства вполне способны проанализировать код и найти, например, неверно передаваемый параметр. С Питоном у меня это реально работало.
Если такое средство видит, что параметр используется как индекс в списке, оно предположит, что тут должно быть целое. Если его индексируют строкой — оно должно быть совместимым со словарём. И так далее. Явные хинты тут не обязательны. Хотя, да, желательны — я бы предпочёл их видеть много где — но в последних версиях Питона, например, это уже делается.
Это если он не сумел вывести тип. А если вывел, но не тот, что думал программист?
Так в том и дело, что он хочет утку, а получается вдруг амадина. Она тоже крякает, но как-то невыразительно :)
Но сравнению это не мешает: если возраст это не отдельный тип со своими операциями, а уточнение Integer, то сравнение выполняется по правилам Integer, и выход разности за допустимые пределы игнорируется. Тем более что сравнение может вылиться, например, в инструкцию SLT процессора стиля MIPS/Risc-V, которая вообще формально ничего не вычитает :)
Есть понятие настраиваемых пакетов, которое практически точно соответствует шаблонным классам C++. Собственно, range задаёт такой же настраиваемый пакет, но встроенного определения.
В общем случае операции над range-типами исполняются так же, как над их базовым типом, а проверка на диапазон производится уже при присвоении целевой переменной (или можно форсировать конверсией к её типу какой-то части выражения). То есть, пока вы результат никуда не присвоили или не проконвертировали, сумма Integer + range 1..120 будет считаться так же, как сумма двух Integer. Если это 32-битные, то переполнение будет диагностировано по выходу любого промежуточного результата за общеизвестные -2147483648...2147483647. Более узкий диапазон, как уже сказал, будет проверяться, если вы присвоите переменной типа Age или напишете что-то в стиле Age(A1+X) как одну из компонент более сложного выражения.
Режим с игнорированием всех переполнений (аналогично unsigned в современном C, всей числовой арифметике на стандартных операциях в Java...) возможен с использованием определений типа mod, например mod 2**32 значит 32-битное беззнаковое с заворотом результата (обрезанием до 32 бит). Если нужно считать таким образом, требуется явная конверсия в такой модулярный тип и обратно. Модулярные — только целые без знака.
Резюмируя, всё это в языке хорошо определено (иначе бы его не приняли для DoD:)), и достаточно оптимально, как для задачи "добиться отсутствия неожиданных эффектов чуть менее, чем везде". Так что Ваши вопросы по системе типов всего лишь требуют внимательного похода в место типа такого.
Поэтому, например, если мы считаем A1+A2-A3, где все три типа Age, и A1+A2 вылазит за его диапазон, но A1+A2-A3 не вылазит, промежуточная ошибка не будет замечена, но если результат всего выражения будет присвоен AR типа Age и вылезет за 1..120, будет исключение.
К вопросу о корректности разности возрастов это не относится.
Чтобы сделать, что разность возрастов была отдельным типом, нужно создать «пакет» в терминах Ada (это лучше всего соответствует «классу» в C++ и аналогах) и для него уже определить function "-". Механизм, таким образом, для этого есть, хоть и громоздкий. Там уже можно определить и все необходимые прочие операции и ограничения этого типа.
Если исключить возможность явной конверсии своих типов данных в стандартные (как целые), то можно обеспечить и типобезопасность для контроля размерностей (например, не присваивать километры миллиграммам). В смысле этих возможностей Ada не уступает какому-нибудь C++, хотя и выражает свои возможности более громоздко.
как по мне, ничем, кроме выбора и группировки ключевых слов, не отличается от
А вот то, что исключения в Ada не параметризованы — не переменные, а только типы — сильно усложняет передачу обстоятельств исключения.
Есть один случай, когда отсутствие скобок таки вредно влияет на восприятие: это поиск конца блока, не охватываемого одним взглядом, а особенно, когда таких блоков несколько. Что-то вида
Вот без этих #end увидеть конец блока может быть очень нетривиально.
Кто-то скажет, что нефиг такие крупные блоки создавать. Я не буду сильно возражать, но случай разный бывает, и код — тоже. (Особенно при отсутствии оптимизатора, который позволил бы вынести действия в отдельную внешне видимую функцию и потом успешно её заинлайнить.)
В остальном — поддерживаю.
120 — почему integer, а не short или long? И даже почему не double?
На примере констант это всё не видно, но как только начинаются реальные операции с данными, тип вдруг начинает иметь значение.
IDE с поддержкой динамически типизированных языков уже существуют и активно развиваются.
> не надо запускать и дебажить для того, чтобы обнаружить глупую опечатку — компилятор и даже среда разработки понимают, что ты написал, и как можно раньше рассказывают тебе о проблемах.
Аналогично, для динамически типизированных языков есть средства линтинга, и профессионалы не выпускают код без него. Для Python я с ходу могу назвать 4 таких средства, для Javascript — 2, для многих других есть встроенные анализаторы (например, в Perl исключение по use strict генерируется именно таким контролем, а не ошибкой собственно рантайма типов).
Но есть таки принципиальная разница. Грубо говоря, для статически типизированных языков то, что вычисляется при компиляции, будет аксиомой при выполнении (объект типа X => у его методов будет именно та сигнатура, что у класса X). Для динамически типизированных это не обязательно — Python, Javascript позволяют подменять подложку реализации на ходу. Линтинг в принципе не способен отловить такие ситуации. Их запрет для возможности статического анализа — уже вопрос административной политики при разработке, а не собственно языка.
> не надо постоянно вручную проверять и преобразовывать типы, на случай, если на вход передадут какую-то дичь. Типичный пример: String( arg ).toLowerCase()
Да, фактор существенный. Мне в этом смысле понравились правила от Hola для Javascript: например, если требуется, чтобы на входе функции было число, пишется +x, а если строка — ""+x. Рантайм умеет опознавать эти ситуации и переводить их в конверсию типа.
Но вот lowercase это уже за пределами обычной возможности системы типов, надо делать свой враппер.
> типы вручную пишутся только там, где пользователю это важно — в остальных местах они выводятся автоматически
Пока что это достаточно малая часть таких языков. И даже в них автоматический вывод работает не всегда и не везде адекватно. А главное — что для уверенности программиста, что тип получился именно нужный, приходится его указывать явно.
Поэтому, при рассмотрении строгости типизации лучше вводить несколько канонических уровней и сравнивать с ними:
1. Вообще никаких неявных конверсий (Go, или близко к нему).
2. Конверсии по умолчанию — между числами, или только явно разрешённые функции, или только в особых контекстах (основной компилируемый майнстрим, C++, C#, Java и т.п.)
3. Размыта граница между строкой и тем, что она представляет (JavaScript, Perl...)
4. Вообще всё есть строка, оптимизации есть, но это принципиально не отменяют (sh, Tcl...)
> И только в гите ветка — это именованная голова, а принадлежность коммитов определяется только по связям и работает лишь до первого мержа.
Этого я не понял, учитывая, что безымянные головы вроде есть только в Hg, а для остальных различие между этими интерпретациями несущественно.
> Отсоединеннный коммит — это вообще уникальная гитофича, похожая до степени смешения на багу.
Оно существует только при особых режимах работы. Меня вот множественные головы напрягают больше :)
> Необходимость помнить, запушен ли коммит, после мерка вымораживает.
Этого не понял совсем. Сравнение с Hg или CVS/SVN? В любой распределённой системе локальный коммит может быть не запушен.
> Любовь к rebase есть любовь к фальсификации истории в угоду чисто эстетического профита от няшной линеечки коммитов.
«Эстетический профит» превращается в реальную потребность понимать, что происходило, при разборе через 1-2-5-10 лет, когда никто не помнит подробностей происходящего. И главное не линейка, а чёткость и законченность каждого отдельного действия, а также разделение действий по характеру (как минимум, ни в коем случае не смешивать форматирование, рефакторинг и изменение функциональности). Линейчатость удобна, но не критична.
> но посмотрев и сравив, выбрали и мерк и не пожалели ни разу.
Ну так не CVS же выбрали (имел я дело с одной конторой в 2015-м, принципиально сидящей на CVS — это был ужас). Но стоит таки оценить, как это повлияло на методы работы с кодом и с задачами.
Да, именно. Если убрать эмоции и само слово «фальсификация», которое уже содержит в себе эмоциональную оценку, то надо начать с признания, что истории, если она не зафиксирована в некоем авторитетном хранилище, вообще нет. Даже без SCM на локальных файлах можно написать любую историю, а с distributed SCM любого вида это становится тривиально — всегда есть возможность взять другую ветку или рабочую копию и повторить начисто. Поэтому вопрос должен стоять так — чего мы хотим от истории? Видеть коммит промежуточного кривого состояния каждый раз, когда программист отшёл в туалет, или всё-таки набор логически цельных изменений? Не знаю, как у вас, а вокруг меня второе таки преобладает.
> Это еще почему? Вы после слияния конфликты не правите, тесты не прогоняете, упавшие сразу не чините?
> Полностью автоматического слияния в общем случае не бывает, ручные правки являются частью процесса.
Именно потому, что ручные правки при merge предполагают правки именно конфликта, а не произвольного места.
Если у вас в foo.h поменялось getFoo() на getFoo(FooContext*), это не повлияет на неправильное использование уже несуществующего getFoo() в bar.c.
Чаще (по тому, что я видел) делают таки правки после мержа, чтобы разделять логически разные действия в разные диффы. Но это требует возможности отменить тестирование самого merge commit.
> Вот только если вы те же самые коммиты склеите ребейзом — вы получите целую цепочку нерабочих коммитов и заведомо неверный результат поиска места ошибки, если она всплывет не сразу.
Именно поэтому я и рекомендовал при таком ребейзе надо править нерабочие коммиты, а не делать правку им вдогон.
Это не настояние на ребейзе вместо мержа или наоборот. Это именно как сделать такой ребейз беспроблемным для будущего поиска.
> У гита есть аналог cherry pick но он ссылку на оригинальный коммит добавляет только опциально и только в комментарий.
Ну я её добавляю всегда. Автоматизация показа этой связи, возможно, полезна, но пока что не сталкивался с такой необходимостью.
> И какой смысл рассказывать про чудо-индекс, если хороший тон — это маленькие коммиты, а mq в мерке кроет индекс как бык овцу?
Не кроет он никак индекс, потому что для представления патча для mq он уже должен быть чистым от прочих изменений.
Я про «до».
> Ну это, в гите наличие индекса существенно усложняет чуть менее, чем весь интерфейс. А пользы — чуть.
Ну вот тут и видна фатальная разница. Для меня это не «чуть», это критично. И соответственно ценность всех остальных механизмов, если они это не поддерживают, снижается в разы.
> Таким образом, без дополнительных телодвижений новые головы просто-напросто не выпинываются.
OK, принято. Я не помню, откуда взял про передачу всех голов — скорее всего, это утверждал один из апологетов Hg, а я не проверил. Осталось не разрешать их локально по умолчанию :)
Я не говорил о публично выставленной части (тут скорее надо сослаться на концепцию Phases), хотя и тут есть разные мнения — иначе бы в `git help rebase` не было секции «Recovering from upstream rebase». Но именно свою локальную работу привести в порядок, по-моему, обязательно.
> вероятно, речь об угадывании rename/copy вместо записывания
Тут я тоже удивляюсь, что мешает в Git добавить запись этих данных, если автор явно хочет. Вероятно, они не считают частный случай настолько существенным (над частичным разбросом содержимого в разные файлы), а детект по содержимому сейчас работает достаточно неплохо (и для log, и для merge).
До последних версий (3.8? я сбился с историей нумерации) он был непригоден. Сейчас надо перещупать заново.
> частичный коммит реализован искаробочным плагином record
1) deprecated 2) принципиальная разница в отсутствии явного индекса, я не могу перед коммитом посмотреть, что же получилось. (В описании вообще подтасовка — «interactively select changes to commit» не подразумевает самого коммита, но он происходит.) Да, можно потом корректировать историю, но это много лишних движений. Ещё и легко ошибиться (strip без --keep потеряет изменения).
> hg shelve (правильно, это тоже искаропки)
И снова без возможности учесть индекс. Как-то его, я смотрю, в Hg совсем не оценили, и всё сравнение в итоге утыкается в него.
> Mercurial же просто записывает всё за пользователем.
Я бы оценил эту фичу в варианте 1) автоматически не включена, 2) препятствует внешнему обмену, пока не указана конкретная голова или не осталась одна. Но в текущем варианте это источник тяжёлых диверсий.
Должна ли история сохранять все опечатки и душевные метания программиста? Я лично живу в мире, где лучше, что автор выставляет некоторое «предложение», и только с момента его принятия оно становится частью истории, независимо от того, какие внутренние процессы привели к нему.
Ссылки на статьи после этого — забавны, но не имеют отношения к текущему обсуждению, и, по-моему, вообще ни о чём. Если требуется рабочее состояние после каждого коммита, то это должна обеспечить автоматизированная проверка. Например, у нас к Gerritʼу для этого был прикручен Jenkins. Если коммиты были отправлены под другую версию, и корректировка под новое API была сделана потом одним рывком, а не на каждый участвующий, то проверка не прошла, и это уже вопрос административный, допускать такое вообще или нет. Но хуже то, что с merge, которое предложено взамен, если это именно merge, то по любому получим неработающую версию после слияния, а если тут же править результат слияния, то это будет уже не честное слияние, а доработка. По-моему, тут идеального выхода нет вообще.
> Оно всего-лишь создаёт скрытую локальную ветку для незаконченной работы. Это даже в SVN не сложно реализовать,
Локальную — в SVN?
> Чувствую у вас не редки ситуации, когда у вас на машине «всё работает», а у того, кто пульнёт ваши изменения, всё сломается, ибо вы то забыли закоммитить, то закоммитили чего лишнего.
Бывают — там, где нет автоматизированной проверки коммита. (Сейчас я расслабился, потому что основную компоненту веду один. Раньше за счёт центрального Gerrit с code review такое не проходило, и приходилось заботиться сразу.) Но доля таких ситуаций всё равно в разы меньше, чем грязи в процессе, когда вначале коммитится со всеми локальными мотлохами, а потом они вычищаются (а часто даже тупо забываются, пока не всплывают на продакшене). Так что чисто статистически от этого тоже польза.
> Я так и не уловил этого тонкого различия. Почему вы противопоставляете часть (голова) целому (ветка)? Ветка метро — это всё множество последовательно соединённых станций, или только лишь конечная станция?
Потому что Hg позволяет быть нескольким безымянным головам одновременно. А это уже в случае метро не одна ветка, а несколько, вплоть до всей сети.
> Ничего удивительного. Git — это этакий прокачанный CVS, который отслеживает не отдельные файлы, а группы файлов, и плевать он хотел на историю их содержимого (которое кочует между файлами).
Я не понял логики этого замечания. CVS следил за каждым файлом отдельно, а Git, оказывается, «прокачанный CVS», но за одним файлом уже не следит.