1. Я не знаю, что такое «TCP\IP». Если Вы про весь стек TCP/IP, то UDP в него входит. Если только про TCP, то не надо к нему клеить «IP».
2. С чего Вы взяли, что у TCP выигрыш?
3. Фреймы подтверждения 802.11 не входят в размер IP пакета. Тем всё равно остаётся 1500, даже если WiFi их дробит на свои фрагменты.
4. Я не говорил про достижение 99% реальной полосы от формально возможного на 802.11. Речь шла исключительно про соотношение потока прямой передачи (данные TCP) против обратной (подтверждения), против Ваших ранее предположенных 50/50 (а иначе сложно понять слова про «что представляет собой пакет в обратную сторону, отсюда и деление на два»). Реальная скорость WiFi зависит от слишком большого количества параметров, чтобы что-то тут утверждать только на основании свойств IP.
> подтверждение доставки, что представляет собой пакет в обратную сторону, отсюда и деление на два.
Не так. TCP может, например, передать в одну сторону порцию (окно) ~60KB как 40 пакетов по полтора килобайта и получить в ответ 40-байтный ACK с подтверждением всего окна, после этого передать следующую порцию на 60KB… конечно, есть какие-то потери на задержки, но достичь >99% занятия канала потоком данных в одну сторону — легко.
Ну почему "даже" :) Что деление через умножение выгоднее, если деление на выбранный делитель делается хотя бы 2-3 раза, известно достаточно давно (с тех пор, как появилось O(1) умножение). Выбирая делитель (размер таблицы у ТС), можно вычислить необходимые параметры (в простейшем случае — множитель, величину финального сдвига и направление округления) и запомнить их до следующей смены размера.
Библиотека по ссылке значительно более продвинута, и может оказаться оверкиллом. Но по крайней мере сильно быстрее divl/divq каждый раз :)
Что касается «новых фич», уверяю, ни в одном из языков (в классической вычислительной парадигме) никогда не будет «новой фичи», которой бы до этого не было в Lisp.
LISP до начала стандартизации Common LISP или вообще весь со всеми диалектами? (тогда — нечестно)
Системы типов с автовыводом, а-ля ML, Haskell?
Или это уже не "классическая вычислительная парадигма"?
Потому, что выдаёт вообще всё, что угодно, кроме того, что там в объекте действительно есть.
Насколько я вижу, всё, что он выдал там, действительно есть в объекте (то есть, при вызове по имени будет поднято по цепочке прототипов и найдено). Да, их много, но самое важное находится вверху списка. Если бы не было такой сортировки, разбираться было бы в разы сложнее.
Только для стандартных API и для JSDoc аннотаций (привет, статическая типизация)
Так это и хорошо, что он умеет читать эти аннотации. Проверить соответствие аннотации сути одной функции просто и это локальное действие, не требуется никуда далеко заглядывать, и потом опираться на эту аннотацию — это как раз то, как это всё может работать в рантайме с "высочайшей" динамичностью типизации.
Эту задачу решают "типы-суммы" и "типы-произведения". TypeScript, например, это умеет.
Хорошо, значит, прогресс в эту сторону идёт.
Скорее динамическую диспетчеризацию (которая приятно дополняет статическую типизацию).
120 — да. 120000, например, уже не обязательно. В C99, например, сказано следующими словами:
The type of an integer constant is the first of the corresponding list in which its value can be represented.
Если на платформе int 16-битный, 120000 автоматически станет long.
Но тип константы тут не так важен. Важнее то, что будет, если мы обопрёмся на это вычисление типа при назначении его переменной, и значение потом будет меняться, а тип — нет. Например, если кто-то захочет опереться на то, что в Java или Go целочисленная арифметика всегда урезает до последних N бит, даже знаковые, и у него тип переменной всегда окажется int64 вместо int32, результаты будут неожиданными. Или в C# написать 2.5 вместо 2.5m; многим такое очень тяжело распознать, даже вглядываясь в код.
Поэтому автоматическое определение типа хорошо, если далее значение переменной не заменяется. "Внутреннее" изменение может быть; есть случаи, когда автоопределение удобно и при изменении — например, итераторы. Но даже "x += 1" уже даёт стрёмный вариант (а что, если это оказался float более чем 2**24 по модулю?), лучше избегать автоопределения и задавать тип явно.
(Хм, тут исключений больше. Например, стандартная арифметика над floatʼами может адекватно работать при любом их типе. Но это опять же не общий принцип.)
Попробуйте посоветовать это Торвальдсу. :)
На самом деле, в простом случае я согласен — очень удобно переложить все проблемы мержа на разработчиков конкретного изменения, заставляя их обеспечивать бесконфликтно прилагаемую ветку. Но это будет работать ой не всегда и не везде. И да, у конкретного автора может не быть права коммитить в общую ветку — поэтому я и говорю про то, что он должен обеспечить бесконфликтный мерж.
Если же политики доступа запрещают девелоперам делать мерж, есть ребейз и черри пик.
Дело ведь не в этом. Ребейз часто удобнее мержа, да, но часто и хуже. Особенно если что-то уже давно разработано и принято.
Ну и будет как с 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...)
2. С чего Вы взяли, что у TCP выигрыш?
3. Фреймы подтверждения 802.11 не входят в размер IP пакета. Тем всё равно остаётся 1500, даже если WiFi их дробит на свои фрагменты.
4. Я не говорил про достижение 99% реальной полосы от формально возможного на 802.11. Речь шла исключительно про соотношение потока прямой передачи (данные TCP) против обратной (подтверждения), против Ваших ранее предположенных 50/50 (а иначе сложно понять слова про «что представляет собой пакет в обратную сторону, отсюда и деление на два»). Реальная скорость WiFi зависит от слишком большого количества параметров, чтобы что-то тут утверждать только на основании свойств IP.
Не так. TCP может, например, передать в одну сторону порцию (окно) ~60KB как 40 пакетов по полтора килобайта и получить в ответ 40-байтный ACK с подтверждением всего окна, после этого передать следующую порцию на 60KB… конечно, есть какие-то потери на задержки, но достичь >99% занятия канала потоком данных в одну сторону — легко.
Ну почему "даже" :) Что деление через умножение выгоднее, если деление на выбранный делитель делается хотя бы 2-3 раза, известно достаточно давно (с тех пор, как появилось O(1) умножение). Выбирая делитель (размер таблицы у ТС), можно вычислить необходимые параметры (в простейшем случае — множитель, величину финального сдвига и направление округления) и запомнить их до следующей смены размера.
Библиотека по ссылке значительно более продвинута, и может оказаться оверкиллом. Но по крайней мере сильно быстрее divl/divq каждый раз :)
LISP до начала стандартизации Common LISP или вообще весь со всеми диалектами? (тогда — нечестно)
Системы типов с автовыводом, а-ля ML, Haskell?
Или это уже не "классическая вычислительная парадигма"?
Насколько я вижу, всё, что он выдал там, действительно есть в объекте (то есть, при вызове по имени будет поднято по цепочке прототипов и найдено). Да, их много, но самое важное находится вверху списка. Если бы не было такой сортировки, разбираться было бы в разы сложнее.
Так это и хорошо, что он умеет читать эти аннотации. Проверить соответствие аннотации сути одной функции просто и это локальное действие, не требуется никуда далеко заглядывать, и потом опираться на эту аннотацию — это как раз то, как это всё может работать в рантайме с "высочайшей" динамичностью типизации.
Хорошо, значит, прогресс в эту сторону идёт.
Да, это правильный термин для данного случая.
120 — да. 120000, например, уже не обязательно. В C99, например, сказано следующими словами:
Если на платформе int 16-битный, 120000 автоматически станет long.
Но тип константы тут не так важен. Важнее то, что будет, если мы обопрёмся на это вычисление типа при назначении его переменной, и значение потом будет меняться, а тип — нет. Например, если кто-то захочет опереться на то, что в Java или Go целочисленная арифметика всегда урезает до последних N бит, даже знаковые, и у него тип переменной всегда окажется int64 вместо int32, результаты будут неожиданными. Или в C# написать 2.5 вместо 2.5m; многим такое очень тяжело распознать, даже вглядываясь в код.
Поэтому автоматическое определение типа хорошо, если далее значение переменной не заменяется. "Внутреннее" изменение может быть; есть случаи, когда автоопределение удобно и при изменении — например, итераторы. Но даже "x += 1" уже даёт стрёмный вариант (а что, если это оказался float более чем 2**24 по модулю?), лучше избегать автоопределения и задавать тип явно.
(Хм, тут исключений больше. Например, стандартная арифметика над floatʼами может адекватно работать при любом их типе. Но это опять же не общий принцип.)
Попробуйте посоветовать это Торвальдсу. :)
На самом деле, в простом случае я согласен — очень удобно переложить все проблемы мержа на разработчиков конкретного изменения, заставляя их обеспечивать бесконфликтно прилагаемую ветку. Но это будет работать ой не всегда и не везде. И да, у конкретного автора может не быть права коммитить в общую ветку — поэтому я и говорю про то, что он должен обеспечить бесконфликтный мерж.
Дело ведь не в этом. Ребейз часто удобнее мержа, да, но часто и хуже. Особенно если что-то уже давно разработано и принято.
Оффтопик: "черри пик" звучит как карта. Черри треф, черри чирв :)
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...)