Как стать автором
Обновить
40
0.2
Valentin Nechayev @netch80

Программист (backend/сети)

Отправить сообщение
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, например, это умеет.

Хорошо, значит, прогресс в эту сторону идёт.


Скорее динамическую диспетчеризацию (которая приятно дополняет статическую типизацию).

Да, это правильный термин для данного случая.

Скажем, в C литерал 120 — это точно int

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++, хотя и выражает свои возможности более громоздко.
Адовский

begin
  Work;
exception
  when X1 =>
     Y1;
end;


как по мне, ничем, кроме выбора и группировки ключевых слов, не отличается от

try
  Work;
catch X1 =>
     Y1;
end;


А вот то, что исключения в Ada не параметризованы — не переменные, а только типы — сильно усложняет передачу обстоятельств исключения.
> При наличие отступов, фигурные скобки не несут никакого дополнительного смысла.

Есть один случай, когда отсутствие скобок таки вредно влияет на восприятие: это поиск конца блока, не охватываемого одним взглядом, а особенно, когда таких блоков несколько. Что-то вида

    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 799-й
Откуда
Киев, Киевская обл., Украина
Дата рождения
Зарегистрирован
Активность