Как стать автором
Обновить

Комментарии 409

Да, хорошая саркастическая статья. Оценил юмор

Оригинал опубликован 1 апреля. Статья соответствует )

Немного толстовато, но в целом норм. Про пару моментом не знал, например.

Не ловкая ситуация, когда читаешь и принимаешь за чистую монету.
НЛО прилетело и опубликовало эту надпись здесь
… и так будет вечно…
Странно, вроде не пятница, а «смИшная статья про JS» есть.

Ну раз завтра выходной, значит сегодня — пятница.

Надо было до завтра дотянуть. Первоапрельская статья, первомайская… Какая разница?! )

День победы! Javascript над Golang

С 9м мая не путаете?

Хорошо что это всего лишь юмор и человек останется гофером. Нам тут клоунов не нужно...

То ощущение, когда знаешь почему все это так работает, почему в этом есть логика и читаешь очередной высер дегенерата, который не способен почитать спеку или хотя бы постараться разобраться что к чему и ожидает иНтуИтИвНо ЯсНоГо яЗыкА. Тройной фейспалм.


Вот не в моих правилах вообще опускаться на личности и так бомбить, но откуда столько людей с синдромом графомании?


Как им времени своего не жалко только эти статейки калякать.


// я понимаю, что это перевод

Вопрос не "почему" оно так работает, а "какого хрена".

Ну если человек искренне считает, что вот эта конструкция в чем-то неправильна:


(1,2,3,4,5,6) === (2,4,6)

То надо не статьи писать бежать, а читать доки про операторы скобок и запятой. И то, что они возвращают последнее выражение.


Это как говорить с Норвежцем и почему-то ожидать, что его слова тебе будут интуитивно понятны.


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

Есть строгие правила — либо ты их знаешь, либо ты не программист. Додумывать своей мудрой головой ничего не надо.
Ох уж этот юношеский максимализм… Объем памяти в голове сильно ограничен. Некоторые языки позволяют запомнить несколько универсальных правил взаимодействия сущностей языка, и на основе них дальше можно писать любые программы. А другие языки кишат исключениями из этих правил, за которыми постоянно приходится лезть в спеку и тратить на это время.

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

В приличных спецификациях на каждое решение есть убедительная аргументация: это решает проблему неоднозначности, это необходимо для оптимизации, это улучшает продуктивность. В джиесе такого нет. Решения, принятые спонтанно по зову сердца, просто оказались с годами высечены в камне. Динамическая типизация? Ну, двадцать пять лет назад это было модно, особенно для крошечного скриптового языка, каким планировался джиес. Убогое стандартное API даты таково, потому что его взяли под копирку из Java. Даже название (!) украдено у Java, чтобы словить хайп того времени.
Ох уж этот юношеский максимализм… Объем памяти в голове сильно ограничен. Некоторые языки позволяют запомнить несколько универсальных правил взаимодействия сущностей языка, и на основе них дальше можно писать любые программы. А другие языки кишат исключениями из этих правил, за которыми постоянно приходится лезть в спеку и тратить на это время.

Возможно вам близок такой подход, мне нет. У технаря должно быть строгое понимание того, что и как он делает. Математика, инжниринг и программирование это не худ. литеретура, где достаточно вдохновится одной книгой и написать похоже другую. Если вам лень или если конкретно у вас ограничен объем того, что можете запомнить — ну это тогда не мои проблемы и не проблемы остальных людей, коих не мало и кто программирует на JS.


Про операторы, нужно или не нужно и так далее и тому подобное — все я это слышал и читал и аргументировал не раз, ибо учитель JS. Но подумайте сами — Java это хорошо для энтерпрайза — ибо у тебя все строго и особой гибкости нет (это в данном случае фича, а не баг). В JS иначе — много флексибилити, много специфик. Трудно, не удобно, не нравится — берешь и программируешь на другом, на том — что нравится. Но какого поливать этот язык грязью я не знаю.


Я могу согласится с некоторыми изьянами языка — например тот же дейт, typeof null, оператор ==. Но это часто совсем не то, за что язык хейтят. Более того это довольно редкие случаи или случаи выбора — никто тебя не обязывает обходит неочевидности ==, просто юзай === и все. Никаких проблем. Но вот такие вот статьи единственное, что оставляют — так ощущение того, что автор просто хотел выплеснуть свою желчь.

В JS иначе — много флексибилити, много специфик. Трудно, не удобно, не нравится — берешь и программируешь на другом, на том — что нравится. Но какого поливать этот язык грязью я не знаю.

Ну так а что хорошего во флексибилити? Для реализации чего-либо в языке необходимо и достаточно иметь всего один простой способ. Если в языке есть много способов, причем ряд из них приводят к трудно обнаруживаемым ошибкам, это плохой язык. Кидайте в меня любыми какашками, но JS, это объективно очень плохой язык, которому круто повезло в своё время оказаться единственным скриптовым языком для очень перспективной платформы.
JS, это объективно очень плохой язык

Любая объективность должна иметь объективные метрики. В студию, пожалуйста.

градус ненависти сообщества подойдет?) или как вариант — количество первоапрельских статей и выступлений на конфах с высмеиванием некоторых решений, принятых в языке) в конце концов ЯП существует для промышленных программистов, в этом смысле количество жалоб на его кривизну — вполне объективная метрика

Вполне объективная метрика того, что язык используют для промышленного программирования, в отличии от тех, которые не используют.

Увы в промышленном программировании (и не только программировании) нельзя просто так взять и отказаться от чего-то, даже когда ситуация круто поменялась и появились лучшие альтернативы. Обратная совместимость очень важна. Есть конечно смельчаки «выкинуть всё старое и переписать с нуля», но они как правило плохо кончают)
В JS иначе — много флексибилити, много специфик.

много специфик не из за того что динамическая типизация и много флексибилити, а просто так случайно получилось, язык «разработали» в спешке, так и оставили ради обратной совместимости. В том же пайтоне при динамической но более строгой типизации, WAT моментов куда меньше.
У технаря должно быть строгое понимание того, что и как он делает.
Полностью согласен. Проблема в том, что джиес заставляет не понимать, а зазубривать. Инженерный подход же предполагает поиск оптимального, элегантного решения, а не закидывание задачи костылями и копипастой.
Если вам лень или если конкретно у вас ограничен объем того, что можете запомнить — ну это тогда не мои проблемы и не проблемы остальных людей, коих не мало и кто программирует на JS.
Если вы гордитесь тем, как мастерски умеете есть кактус и почти не колетесь — ваше право. До относительно недавнего времени это было неизбежно, т.к. джиес занимал уникальное положение и не имел альтернатив. Но сейчас им достаточно наелись, что появились Typescript, WebAssembly, ReasonML и наконец-то ситуация стала улучшаться.
автор просто хотел выплеснуть свою желчь
С этим тоже согласен. Но «желчь» подана тонко и вполне оправдана.
Я понимаю, о чем вы говорите. Но я искренне не понимаю, зачем писать такой код в JS. Может, человек ожидал наличие кортежей (сарказм), может еще что-то, конечно, но все же. Ни в одном языке не нужно использовать конструкции, с которыми ты не знаком. И о типах, да, я согласен, что здешняя система типов местами отвратительна, но ее действительно нужно знать. В C++ типизация тоже не на высоте, но никто в здравом уме не будет отрицать, что этот язык очень важен во многих сферах, и нужно знать о некоторых нюансах его реализации, чтобы его использовать.
Но как рофл — статья действительно хороша. xd
P.S. Как поживает ваш компилятор LENS? Будут ли новые статьи на тему компиляции?
C++ никто и не считает образцом изящного дизайна ЯП, скорее наоборот.

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


Компилятор LENS, к сожалению, поживает примерно так же, как Ленин в мавзолее — доступен на всеобщее обозрение в текущем виде. Предел существующей архитектуры достигнут, чтобы двигаться дальше необходимо переписать все с нуля. Времени и мотивации на это нет, так что занимаюсь другими проектами.

Я вас понял. Немного жаль, конечно. Во всяком случае, всегда буду рад снова увидеть ваши оригинальные статьи на любую другую тему. Удачи с вашим проектом!
Я вообще не понимаю, какого рожна в программировании должно быть хотя бы что-то интуитивно понятно? Есть строгие правила — либо ты их знаешь, либо ты не программист. Додумывать своей мудрой головой ничего не надо.

Сами дойдёте до понимания, когда вам понадобится взять на поддержку/доработку проект от других разработчиков на не очень хорошо знакомой вам платформе. Если код интуитивно понятный, вы садитесь, и реализуете функционал. Если код состоит из хрен-пойми-каких конструкций, вы садитесь, и начинаете учить строгие правила (или не строгие, это уж смотря какая платформа). И знаете, второе вы будете искренне воспринимать как тупейшее занятие, когда у вас юность позади, а эта платформа — тридесятая в череде уже изученных. И будете справедливо считать разработчиков, применяющие не интуитивные конструкции там, где можно было сделать то же самое читабельным и понятным, феерическими дятлами.

Понимаю ваше разочарование в такой ситуации — но, тем не менее, язык не виноват. Виноват ваш шеф, который садит почему-то далекого от этого языка человека за поддержку проекта, виноваты возможно клиенты, что торопят со сдачей. Виноваты вы, поскольку у вас юность позади и вы в ней этот язык не выучили (утрирую, но думаю суть понятна).


Но не язык. Конечно проще обвинить JS, а не что-то другое. Но от этого он хуже не станет. Хейтерс гонна хейт )

Понимаю ваше разочарование в такой ситуации — но, тем не менее, язык не виноват.

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

Те языки, которые вообще не требуют усилий для написания адекватного кода очень ограничены. Вы сами сказали, что говнокод можно писать на чем угодно, но почему вы уверены, что для написания нормального кода на js (es6, TS) требуется усилий больше, чем на любом другом? К тому же везде используются линтеры, которые предотвращают написание плохого кода. Я не знаю что у вас происходит на работе, но такое чувство (по вашим комментариям), что компании вообще пофигу кого нанимать, "лишь бы работоло", а как работает — наплевать.

а в принципе требует усилий, чтобы этот самый код был адекватным.

Что разумеется неверно. Или всегда писать === вместо == для Вас усилие? Ну да, печатать дольше. И да, это один из минусов JS, т. к. == можно было бы выкинуть, а === сделать на 1 символ короче. Но не такой, что всё, катастрофа. Я написал ниже хороший комментарий с пояснением всех пунктов из этой статьи.

Потому что "интуитивно-понятность" позволяет программировать эффективнее, делать меньше ошибок и легче их находить. Веский довод для совершенствования языков.

Давайте засунем это в сишарп и посмотрим что он скажет:


var x = (1,2,3,4,5,6) == (2,4,6);

CS8384 Tuple types used as operands of an == or != operator must have matching cardinalities. But this operator has tuple types of cardinality 6 on the left and 3 on the right.

Как по мне, это куда более логичный результат чем false, и особенно true.

или в питон

>>> (1,2,3,4,5,6) == (2,4,6)
False
>>> (2,4,6) == (2,4,6)
True
Или в Delphi:
  var x := [1,2,3,4,5,6] = [2,4,6];
  // FALSE
  var y := [2,4,6] = [2,4,6];
  // TRUE

Но если
  var x1 := [1,2,3,4,5,6];
  var x2 := [2,4,6];
  var x3 := x1 = x2; // E2008 Incompatible types
В JavaScript для квадратных скобок тоже всё «нормально»:
[1,2,3,4,5,6] == [2,4,6]
// false
[2,4,6] == [2,4,6]
// false
Упс

Что значит упс? У Вас два разных объекта, с чего вдруг они должны быть равны?


Если бы это было не так, у меня были бы претензии к языку. Это было бы достаточно странно, что равны разные объекты.


А оператор "," в JS — очень крутая вещь, не раз меня выручал. То, что Вы не знаете юз-кейсы, не значит, что их нет.

У вас случайно нет претензий к JS, что "foo"=="foo"? Объекты-то разные.
Объекты-то разные.

Эм, кто Вам сказал? В данном случае это скорее всего будет один объект внутри движка (по крайней мере в V8).


В любом случае, со строками у Вас нет возможности проверить, одинаковый ли это объект или разный. А значит, можно сделать, чтобы === сверял содержимое строки, а не ссылку.


С обычными объектами так сделать нельзя — они действительно разные не только внутри движка, но с и точки зрения программиста. И если мы скажем программисту, что они одинаковые, получится большой фейл.


В итоге "aaa" и "aaa" — это абсолютно одинаковые объекты с точки зрения программиста, поэтому === говорит, что они одинаковые. А вот {a: 1} и {a: 1} — это два разных объекта с точки зрения программиста.

Эм, кто Вам сказал? В данном случае это скорее всего будет один объект внутри движка (по крайней мере в V8).

Не вопрос, давайте сделаем так


"foo1" === ("foo" + 1)

В итоге "aaa" и "aaa" — это абсолютно одинаковые объекты с точки зрения программиста, поэтому === говорит, что они одинаковые. А вот {a: 1} и {a: 1} — это два разных объекта с точки зрения программиста.

А этого вы не можете знать. Например, с моей точки зрения вполне вероятно что {id: 10} и {id: 10} — это один объект.

А этого вы не можете знать. Например, с моей точки зрения вполне вероятно что {id: 10} и {id: 10} — это один объект.

Нет, в данном случае это разные объекты, т. к. объявлены два раза. Чтобы они были одинаковые, Вам нужно скопировать ссылку на него в другую переменную и уже после этого сравнить. Но в этом случае сравнение как раз и выдаст, что объекты одинаковые.


Это как с близнецами — если они выглядят одинаково (например, на фото), это не значит, что это один и тот же человек. Чтобы это проверить, нужно использовать оператор ===.


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


При этом со строками такой проблемы нет, т. к. изменить строку в JS невозможно (можно изменить ссылку в переменной, но содержимое самой строки). Поэтому даже если строки на самом деле разные, мы можем считать их одинаковыми — это никогда не приведёт к ошибкам.


Причём если бы это было не так, то и со строками в JS было бы тоже самое, что с обычными объектами: "aaa" !== "aaa".

У меня по бизнесу часто бывает нужно трактовать такие объекты как одинаковые (базовый пример — когда я иду делать апдейт в базу а у меня есть два объекта с одинаковой айдишкой и разным value). Так что


В итоге "aaa" и "aaa" — это абсолютно одинаковые объекты с точки зрения программиста, поэтому === говорит, что они одинаковые. А вот {a: 1} и {a: 1} — это два разных объекта с точки зрения программиста.

Вот я — программист, и у меня есть необходимость как различать "aaa" и "aaa", так и считать одинаковыми {a: 1} и {a: 1}

У меня по бизнесу часто бывает нужно трактовать такие объекты как одинаковые

Для этого есть deepEqual.


Строка (также как и обычные числа) считаются простым неизменяемым значением, поэтому сравниваются по содержимому, а объект сложным изменяемым, поэтому сравнивается по ссылке (причины объяснены выше — всё дело в изменяемости).


Если нужно сравнить по содержимому, юзают deepEqual или equal, а не ===.


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

часто бывает нужно трактовать такие объекты как одинаковые

Дайте универсальное определение "одинаковости"

НЛО прилетело и опубликовало эту надпись здесь

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


2 разных объекта совсем не ведут себя как одинаковые сущности. Мы начинаем изменять объект, и если мы считали их одинаковыми, будет фейл. А, к примеру, строки и числа ведут себя как одинаковые сущности — что бы программист не делал, невозможно написать такой код, чтобы можно было отличить их, либо чтобы их различие/одинаковость привела к ошибкам в коде. Причина этого проста: числа и строки нельзя изменять. И да, это касается не только строк, но и чисел: в памяти две "5" (пятёрки) не значат, что это два одинаковых числа, в реальности скорее всего они будут продублированы, т. е. это будут две разные "5". Но программист никак не может это узнать и написать код, который бы привёл к ошибке из-за считания их одинаковыми. Всё потому, что "5" никогда не изменяется, а изменяется только значение переменной. Сама "5" так и остаётся "5" навечно.


Как итог: числа и строки считаются одинаковыми сущностями, объекты нет.


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

НЛО прилетело и опубликовало эту надпись здесь

Не очень понял, что тут не так. P выдало разные результаты, и при этом в JS 5 !== "5". Всё работает как надо.

НЛО прилетело и опубликовало эту надпись здесь

Я так понимаю, Виталий Второй хотел сказать "числа считаются одинаковыми сущностями и строки считаются одинаковыми сущностями".

Разумеется, имелось ввиду, что, к примеру, число 5 и 5 — это в JS одинаковые сущности, как и строки "5" и "5". Выполняются равенства (5 === 5) и ("5" === "5") (т. е. они одинаковые между собой).


Конечно есть фишки с даблами типа 0.1 + 0.2 !== 0.3, но это потому что сам процессор при сложении 0.1 и 0.2 получает другое число (не 0.3) из-за потерь точности. Это во всех языках так.


Это достаточно логично, что, к примеру, 5 и 5 равны между собой, даже несмотря на то, что в памяти это совсем разные пятёрки. Почему так и должно быть, я объяснил выше. JS максимально логичен здесь. А вот другие языки не всегда, как я догадываюсь. Если строки могут изменяться, то ок, тогда правильно (но тогда бы неплохо ввести неизменяемые строки). Но если не изменяются, и их нельзя сравнить с помощью ===, с языком явно что-то не так. Это тоже самое, если бы 5 было бы не равно 5, а для сравнения нужно было бы писать 5.isEqual(5). Смысла в этом ноль, т. к. не существует случая, когда сравнение 5 === 5 подвело бы программиста (почему — написал выше). Аналогично и со строками.

С точки зрения JS (да и некоторых других языков) == проверяет именно на одинаковость, на одинаоковое значение, а === на идентичность, на тожесамость.


Для скалярных типов данных одинаковость и идентичность синонимы по сути, а вот для ссылочных — нет. Два объекта могут быть одинаковыми, но === проверяет для них не одинаковость, а идентичность один и тот же объект справа и слева или разные, пускай и одинаковые.

так себе пример, поскольку в js массивы это объекты, а объекты так сравнивать нельзя.
Потому что так решили разработчики получилось. Обьектно-ориентированный язык — а обьекты сравнивать надо каким-то хитрым способом…
1. Он не объектно-ориентирован, а прототипно-ориентирован. Всё ООП в js — то натяжка совы на глобус (ради удобства программистов ООЯП).

2. Он не единственный язык, в котором нельзя вот так прямо сравнивать 2 объекта. И это логично, поскольку надо различать сравнение объекта с самим собой и с таким же, но другим объектом.
При простом сравнении идёт сравнение ссылок на объект в памяти.
Он не объектно-ориентирован, а прототипно-ориентирован.

Как минимум, прототипно-ориентирован — это и подразумевает ООП.


А во-вторых, как его там ориентировали не знаю, но язык имеет очень красивое ООП.

В современных Дельфях стало можно объявлять переменные, не указывая тип?

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


А вообще в js количество ВТФ после определённого предела сильно падает. Из последнего, что вызывало втф — работа с датами, точность при делении и, цук, неочевидное наличие this в static методах.


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

Когда после js берешь python, там тоже хватает wtf моментов и ощущение странного, кривоватого дизайна тебе долго не покидает.
Думаешь, ну емае, в js было все попроще и логичнее. :)
Было бы интересно увидеть примеры.
Ну например, отсутствие switch огорчает тех, кто к нему прикипел душой.
Неявный return например. Не очень понятные импорты. Странная работа с файлами, похожая на with из js, отсуствие switch, да. Неуклюжие пляски с virtualenv.

Нельзя сказать что эти мелочи прям ужасные, но таки кажутся раздражающей архаикой. В процессе работы все это сходит на нет, привыкаешь ко всему, тем не менее не могу сказать, что python прям стройнее и удобнее js.
В примере Delphi в 1-м случае — множества целых чисел, а во 2-м — векторы целых чисел (внезапно).
в python, как и в шарпе — кортежи

Не хочу влазить в холивар, но в js tuples вообще нет и конструкция все таки достаточно однозначная — скобки работают как группировка параметров и их можно опустить, а значит это эквивалентно
var x = 1,2,3,4,5,6 == 2,4,6;


Зная что означает операнд «,», никаких вопросов не должно быть. Разве что операнд странный, но он не только в js есть, а в том же С. И если там парсер съест скобки (почему бы и нет, это опять же как группировка), результат будет тем же. Да даже в C# до появления tuple результат такой же. Странно ожидать поведения как в каком-нибудь го от конструкции, которой вообще нет в ja.

PS, не совсем эквивалентная, потому что он сравнивает результаты раскрытия скобок, и будет 6 в каждой из них. В моём «эквиваленте» он сперва сравнит 6 и 2, а потом отбросит этот (как и остальные результаты), и вернёт 6 как результат всей конструкции, но это допущение ради наглядности

На самом деле не эквивалентно, потому что в декларации var запятая разделяет объявляемые переменные, и в данном случае будет ошибка парсинга:
var x = 1,2,3,4,5,6 == 2,4,6;
// Uncaught SyntaxError: Unexpected number

Потому что в C# это Tuple, в Питон это массив, а в JS это comma-оператор. Очевидно ведь.


Вот как вы звучите:


5 % 3 = 2. А!!! ЧТО ЗА БРЕД 3 — это не 2 % от 5.

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


Ну вот к примеру рубист придёт и напишет:


function f(x,y) {
    x*x + y*y;
}

А потом будет кричать: "ааа, джаваскрипт плохой, в руби оно вернуло бы значение". Ну глупо ведь.


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


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

Потому что в C# это Tuple, в Питон это массив, а в JS это comma-оператор. Очевидно ведь.

В Python это не массив, а тоже tuple.
В Python вообще не используют термин «массив»: вместо массивов там списки.

Это проблема ваших ожиданий, а не языка.

Просто какие-то языки разрабатываются по «принципу наименьшего удивления», а какие-то — по принципу «потому что я так решил».
В Python это не массив, а тоже tuple.
В Python вообще не используют термин «массив»: вместо массивов там списки.

"Отвратительный и непонятный язык! Как таким вообще можно пользоваться? Нужно срочно писать ироничную статью!

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

а какие-то по принципу «потому что я так решил»

Явно не в данном случае, т. к. использовать запятую как оператор — это очень логично, т. е. языки C, C++, Javascript, Perl достаточно логичны здесь.


какие-то разрабатываются по «принципу наименьшего удивления»

Ну вообще синтаксис JS во многом взят из C и C++, и как раз наименьшее удивление, думаю, будет, когда запятая будет работать как оператор. Т. е. это не только логично, но и более привычно.


Конечно можно привести аргументы и за другое поведение запятой, но это уже на вкус и цвет… Хороший аргумент — возможность быстрого сравнения кортежей сразу из нескольких значений. Но теоретически в будущем может появиться что-то типа такого: {{5, 6, 7}} === {{6, 7, 8}}

С, C++ и особенно Perl уж никак нельзя считать образцами грамотного синтаксиса.

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

Нет, если язык не оправдывает ожиданий, это может быть и проблема языка. А то так-то и с С++ нет проблем, просто у вас неоправданные ожидания что x + 1 при переполнении может быть меньше чем x.


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


а в JS это comma-оператор. Очевидно ведь

Не очевидно

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

Какие языки в 1995-м году записывали так таплы?

Python?

То JS обязательно должен был сделать этот кусок так же как в одном другом языке, который на тот момент был малопопулярен. Правильно?

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

ML и его потомки, Haskell.
Другой вопрос – какие языки, кроме C и C++, записывают так «вернуть правый операнд»?

Perl
классическое «не баг, а фича». Наличие документации к кривому решению не придает ему элегантности

Посмотрел, что это вроде бы как первоапрельская шутка. Ах шутка. Шутка же, ну — че ты?


Так вот, есть одно видео — вроде тоже "шутка" — https://www.destroyallsoftware.com/talks/wat
Тоже про JS в том числе. И я сам знаю массу людей, которые посмотрев такое видео совсем не будут думать так:


Ах это же незатейливая шутка, пойду-ка почитаю спеку и начну понимать!

А будут думать вот так:


Ох е*ать, да я такое гавно даже трогать не буду.

Поэтому это никакая не шутка, а просто безосновательный хейт.
Почему-то автор не решил так пошутить про свой любимый Go.

Может потому что гоу логичней Js. Да в гоу не хватает некоторых вещей, но там хотя бы с логичностью все нормально.

В го тоже есть не однозначные вещи. Изменится ли поле ban после выполнения цикла?


type Person struct {
    name string
    ban bool
}

func main() {
    persons := []Person{Person{name: "Alex", ban: false}}
    for _, person := range persons {
        person.ban = true
    }
}

Тут неоднозначности нет. Тут достаточно знать указатель или нет. Действует, везде и всегда одинаково. В других языках так же работает.

В скриптовых языках, включая и JS, нет не-указателей, поэтому всё действует ещё одинаковее.
Может потому что гоу логичней Js. Да в гоу не хватает некоторых вещей, но там хотя бы с логичностью все нормально.

Не может. Гоу — отвратительный и крайне нелогичный язык. Хуже всего, что он притворяется статическим, а на самом деле таким не является. А ещё у него дизайн намного сырее, чем в ЖС.

Хотя мне нравится больше js (es6, ts), но я не могу согласиться с тем, что го отвратительный язык. Сами разработчика языка говорили для какой группы людей он предназначен. Если мы возьмем языки с низким порогом вхождения (Js, PHP, Python, Golang), то я бы больше рекомендовал Go новичкам, т.к. именно он накладывает больше ограничений на стиль написания, те же типы. Т.е. для меня сейчас Go это как паскаль раньше.
Насчет дизайна я не понимаю что именно вы имеете ввиду.
именно он накладывает больше ограничений на стиль написания, те же типы

Он накладывает ограничения слишком слабо. Из статических языков с низким порогом вхождения хороши C# и TypeScript. А Гоу — отвратительный язык, который прививает отвратительные практики. У него WTF не на уровне "вот метод странно называется", а на уровне "какой идиот так сделал архитектуру языка?"


Вот пример, первый комментарий: https://habr.com/ru/company/mailru/blog/341822/#comment_10510426

Я бы не рекомендовал новичку TS просто потому что в TS есть any. Даже если взять юзкейс коллбэка. Вряд ли новичок будет писать тип функции коллбэка…
Насчёт шарпов — я бы не сказал, что шарпы на равне с Го по лёгкости изучения (хотя шарпы был вторым языком после плюсов, которые я изучал в 2014).
Я согласен с вами, что Го это не самый лучший язык для решения определённых задач, но я писал именно про то, что для новичка он бы подошёл и сравнил его с паскалем (лично нас обучали Паскалю в школе).
А вот дальше, когда человек понимает прелесть ООПа, понимает смысл документировать свой код, абстрагировать модули, тогда да, Го с этим справляется не очень хорошо (если брать пункт про ооп, то не справляется вообще)
А про ограничения я имел ввиду то, что го накладывает ограничения на стиль написания кода (у него есть офф форматер и компилятор ругается на неправильный стиль)
Насчет прививает отвратительные практики — новички не будут писать настолько сложные программы, чтобы вышли за пределы общих практик

Я бы не рекомендовал новичку TS просто потому что в TS есть any

Который стараются не использовать в реальном коде в отличие от того же Go.
А в Go any — это просто идеология и часто — единственный путь. Я про interface{} если что.

Я помню interface{} в го, я помню боль xD. Это из личного опыта, а не стёб, если что =)
Вы правы что any стараются не использовать, но я всё же говорил про новичков в программировании. Лично я помню свои первые ощущения от разработки ПО (О, это работает, ура! Нужно добавить больше функционала), а при таком настрое наврядли люди задумываются про документированность (да, я считаю, что указание типов относится к документированию).
Наврятли новички зайдут настолько глубоко, чтобы у них было многое завязано на interface{} (вспоминая свою первую учебную программу, где я просто реализовал туду, там с interface{} я сталкивался только при сериализации данных)

Просто странно, что в TS для вас присутствие any — аргумент, а в GO — нет.


Я бы не рекомендовал новичку TS просто потому что в TS есть any

Хотя в TS в отличии от Go можно вообще без any, а в Go рано или поздно приходишь к тому, что выразительности не хватает

Дап сам не знаю на 100% почему в Го это не такой сильный аргумент как в TS для меня. Возможно, в Го ты не так быстро столкнешься с interface{}, как в TS. Возможно при просмотре кода других людей я чаще встречал any, чем interface{}
Насчет выразительности я согласен, но боюсь, что нас другие могут не понять, почему мы го обвиняем в этом =)

Вы видели эту статью? По-моему программа на сотню строк "обойти дерево" (третья структура данных которую проходят в институте, после массива и списка) достаточно простая, но проблем даже с этим хватает.

Я не говорю про всех, возможно это только в моей "шараге" студенты толком не разбираются со структурами. Но по правде, у нас до 4 курса доживают 5 из 30 студентов.
Под новичками я всё же имел ввиду именно начинающих погружаться в мир программирования. Даже сравнил Го с паскалем.
Я не спорю с тем, что на Го неудобно решать какие-либо задачи. Но вы согласитесь с тем, что на Го удобно запустить сервер из коробки?

Мне кажется, что изучать базовые структуры данных и какую-нибудь сортировку пузырьком стоит намного раньше, чем запускать веб-сервер из коробки. "Новичок" в программировании в моем понимании это как раз студент-первокурсник, который только после школьного рисования блокнотом в хтмл приходит в универ, где ему дают какой-нибудь паскаль/си (реже — питон), и начниают говорить, что вот есть циклы, вот массивы, а вот еще списки и деревья.

Хорошо, что мы с вами поняли, что говорим о разных "новичках" =)
Не знаю поверите вы или нет, но для новичка (любого) интереснее что-то создавать, а не изучать как всё устроено внутри.
Я не спорю про то, что знать как всё устроено внутри (в стандартных функциях либо структурах) нужно, но это зачастую не так интересно новичкам, как просто настроить окружение и писать чисто бизнес логику приложения, без продумывания архитектуры без документации.
Возможно вы гораздо старше меня и не помните почему сами окунулись в изучение программирования.
Я не обсуждаю что правильно, а что нет, я лишь накладываю свои эмоции от знакомства с разработкой на возможные ситуации, если бы начинал знакомство с программированием на других языках

Прекрасно знаю, о чем речь, потому что сам создавал на юкосе в конструкторе сайты на вбуллетине для клана в травиане. Копался в php, пытался там аватарки настроить, и мнил себя кулхацкером.


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


Я отлично помню как я окунулся в программирование. В конце нулевых я пошел на курсы в институт, чтобы подготовиться к ЕГЭ. Помимо всего прочего решил походить на информатику, там доплата была типа 1000 рублей в месяц. Прихожу, нас сажают за стол, на доске объясняют всякое, часа через пол говорят садиться за компьютеры и написать программу: определить, оканчивается ли число на цифру. Достаточно быстро сделал, мы показали наши результаты, урок закончился, я пришел домой. И стало мне интересно: как проверить, заканчивается ли число на другое число? В итоге просидел несколько часов, так крутил, эдак, в итоге родил нечто с двумя циклами и пятком goto, которое как-то в франкенштейновском стиле работало. Тут и вопросы пришлось решать, а что если число вообще меньше чем делимое (для цифр такого кейса нет), и вообще как это сделать думать..


В общем, мне кажется именно вопрос "а как вот так сделать" и определило мою профессию.

Ну блин, такой интересный рассказ, чем-то похож на мою историю (тоже сайтики, дкп система для клана и т.п.), а в итоге вы в последнем абзаце написали "а как вот так сделать", что больше относится к написанию нового (по-моему мнению), а не "а как это работает", что относилось бы к изучению =)
Всё же я считаю пример с бензопилой не совсем уместным (там травмоопасно =) ). Да и вы сказали, что обучать нужно с азов, а что если некому обучать?) Что если у вас есть желание творить, вы хотите создать тот же сайт, как быстро в вас погаснет искра, если начинаете изучение со структур?)
Лично я начал изучать структуры в школе, на оллимпиадном программировании, но к тому моменту я уже разрабатывал другие программы несколько лет =)
Ладно) мне кажется невозможно дать объективный ответ.
Кстати там пониже мы обсуждаем как раз поверхностные знания и к чему это приводит xD

А будут думать вот так:

Ох е*ать, да я такое гавно даже трогать не буду.


Мне кажется у языка тут больше заслуга чем у wat видео.
Ну да, прочитает кто-то такую статью и решит не становиться js-программистом, в мире появится на полтора псевдо-десктоп приложения меньше и где-то будет остывать незанятый транскомпиляцией процессор. Ужасная картина, прям антиутопия.
Человек, который не может переплюнуть, старается оплевать.
От создателей «Брат за брата — за основу взято.» и «Он обидел, слёз не видел.» Как Вы подтвердите истинность вашего утверждения?
Он просто за цитаты и двор стреляет в упор.
Ок, давайте так, есть правила написания хорошего кода, JS не соответствует им. Если я напишу в проекте метод сортировки, который будет сначала приводить элементы массива к строке, а потом сортирует и назову его не «sortAsString», а просто «sort» — меня по головке не погладят.
В частности поэтому JS это кал говна, а не язык. Я не могу помнить всю документацию наизусть и знать что там себе нафантазировали те кто писал реализации.

Я надеюсь вы понимаете то вы спорите об default-ом значении аргумента компаратора? Т.е. лексикографически сортировка производится ТОЛЬКО тогда когда вы сами так захотели. В противном случае вы просто указываете нужный компаратор и всё работает как вам надо. Причина почему .sort сам не угадывает какой компаратор вам нужен кроется в динамической природе типизации. У вас в нём могут быть как строки, так и числа, и объекты и пр…


Т.е. в том что .sort без явного указания метода компаратора сортирует всё лексикографически всё правильно. Единственное что можно было сделать вместо этого, это вообще не позволять метод sort без указания компаратора. Ну тоже такое спорное решение.

$ python
Python 2.7.16 (default, Mar 20 2019, 12:15:19)
>>> n = [-2, -7, 0.0000001, 0.0000000006, 6, 10]
>>> n.sort()
>>> print n
[-7, -2, 6e-10, 1e-07, 6, 10]

??

и что вы хотели этим показать?


m = [-2, "-1", "AAA", -10, 10, 100, "BBB", "ZZZ", "1000"]
m.sort()
print m
[-10, -2, 10, 100, '-1', '1000', 'AAA', 'BBB', 'ZZZ']

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


Но не зная этого факта наверняка я бы не решился использовать этот .sort() как есть. Причём ни в python, ни в javascript. Нельзя писать такой код вслепую на удачу. А это приводит нас к тому, с чего мы начали — это всего лишь спор о дефолтном компараторе в методе сортировки массива.

Действительно, когда сортировка сортирует числа как числа, вероятно этот подход в чем-то лучше. А когда она разделяет объекты по их классам — наверно еще лучше. Особенно, когда для этого не делается лишнего преобразования каждого объекта. Вообще, ощущение, как будто ожидаемые результаты операций с дефолтными параметрами — это очень неплохо.

Я честно говоря с трудом могу себе представить зачем мне может потребоваться отсортировать массив выше так, как это сделал python. Какую задачу это решает? Правда я с трудом понимаю зачем мне в целом такой массив может потребоваться. Из реальных примеров это когда у вас массив чисел состоит по факту из чисел и строко-чисел (такие грешки в JS мире встречаются часто). Но тогда обе сортировки дадут убогий результат. Так что пожалуй соглашусь, что default comparator в python выбран несколько лучше. Но я решительно не вижу никаких "WAT?" в лексикографическом варианте. Я нахожу это очень даже логичным и последовательным поведением. Просто в python решили сделать более продвинутую версию. Молодцы.

Второй питон уже даже официально не поддерживается. В третьем же всё нормально: TypeError, так как строки и числа не сравниваемые.

Полностью поддерживаю, хоть и написано несколько в грубой форме. 90% моментов описанных тут из-за не знания языка. Пример с (2,4,5,6) === (2,5,6) вообще смешон. А чего автор ожидал от такого сравнения? Ему не знакомо как работает оператор, или оператор ()? Вот этот пример очень хорошо отображает суть статьи — чем меньше знаний о языке имеешь тем больше моментов тебе (считаешь глупыми) не понятны. Повторюсь с остальными так же. Везде есть объяснение.

Пример с обфускацией тоже глупый. Обфуцировать можно любой язык. И придумать там аналогичную строку, которая будет исполняться. Возьмите хоть тот же BF)))

Мне только не ясен пример с коммутативностью. Плюс параллелизм в JS я не особо знаю, но вроде он не сильный — да. Остальное всё просто школьные выдумки о якобы «плохом языке», а на самом деле о слабых знаниях автора, либо же просто желание автора поджечь очередной холивар.
НЛО прилетело и опубликовало эту надпись здесь

https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex


Если свойство lastIndex больше длины строки, методы test() и exec() завершатся с неудачей, а свойство lastIndex будет установлено в 0.

Кстати, по вашей же ссылке:


Как и при вызове метода exec() (или при совместном с ним вызове), метод test(), вызванный несколько раз на одном и том же экземпляре глобального регулярного выражения, будет начинать проверку с конца предыдущего сопоставления.
НЛО прилетело и опубликовало эту надпись здесь

Просто статья завалена бредовыми примерами. Вот это вообще какой-то угар:


Типы могут меняться в самых неожиданных местах, что помогает программисту не заснуть во время долгих рабочих ночей:

typeof([1][0])
// number
for(let i in [1]){console.log(typeof(i))}
// string

Начнем с того, что typeof это оператор, поэтому не совсем понятно зачем он там юзает скобки. Ну это косметика, не важно.
Важно то, что в первом случае он делает


typeof 1 // что логично - намбер

Во втором он делает


typeof "0" // так как итерирует над ключами объекта, а первый ключ объекта массива это "0".

Что можно вообще думать о познаниях автора?


Следующий пример не лучше же — сам дергает toString (ну ему это, конечно, не известно, так как зачем читать как работает ==) — и удивляется, что переменная то меняет свое значение.

Что можно вообще думать о познаниях автора?

Мне кажется, что с познаниями у автора всё нормально. Просто нужен был материал для статьи :) Всё равно те, на кого эта статья ориентирована, не полезут разбираться во всех этих for-of, for-in, for;; и т.д… А значит пример задаче пригоден.


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


P.S. всякий раз когда мне приходилось залезать в C++ у меня бомбило похлеще Хиросимы. Но я не бежал писать статью про всякие глупости.

Хм… Знали бы вы, как бомбит от C++ людей, которые реально на нём пишут :-D
И, тем не менее, пользуемся.

P.S. всякий раз когда мне приходилось залезать в C++ у меня бомбило похлеще Хиросимы. Но я не бежал писать статью про всякие глупости.

А кто-то пишет

Да тут вся статья, как и всегда в подобных случаях кстати, основана на примерах, которых в реальном мире и не встретишь. Ну может в очень специфичных местах или по незнанию. Не сталкиваются реальные люди со всей этой дичью настолько часто, чтобы это мешало.
А если еще и с typescript, то все эти приколы встречаются примерно никогда.
Здесь в js плохо то, что если работаешь с массивом, в котором элементы пронумерованы от 0 до N, и по-другомы быть никак не может, то ожидаешь что эта прогрессия будет иметь числовое выражение, а не строковое.
Я однажды потратил часов пять на поиск бага, связанного с этим поведением.
После чего сделал шпаргалку из косяков JS и перед тем как искать проблему в своем коде, сверяюсь с этой шпаргалкой.
И знаете что? Стало реально проще работать! Предположение, что это не я дурак, а автор языка, стало экономить просто прорву времени.

Всё же использовать for-in не имея ни малейшего представления о том, что это такое и жаловаться, что оно работает не так как ожидал, это странновато, нет? :)


Какой язык не возьми, если писать на нём в стиле "я что-то загуглил, скопировал в код и оно кажется работает" — проблемы не заставят себя долго ждать.


А если с динамически типизированными языками до этого в целом неработали (будь то ruby, php, js, python) то слепое следование своим привычкам, скажем, из Java, C#, C++ очень часто будет приводить к печальным следствиям.


Пассаж про то, что автор языка дурак, потому что в JS ключами у объектов могут быть только строки и символы не ясен.

for-in в Python и в Ruby работает как раз так, как автор ожидает.
Т.е. дело не в динамически типизированных языках в целом, а в неочевидном синтаксисе конкретно JS.
for-in в Python и в Ruby работает как раз так, как автор ожидает

Беру пример из документации:


fruits = ["apple", "banana", "cherry"]
for x in fruits:
  print(x) 

результат: apple banana cherry
что я ожидал из ваших слов: 0 1 2


Т.е. работает он как for-of в JavaScript-е:


const fruits = ["apple", "banana", "cherry"]
for (const x of fruits) 
  console.log(x) 
// apple banana cherry

Может быть вы имели ввиду другую конструкцию?
P.S. ruby проверять лень, давайте остановимся на python-е

Так автор и ожидал поведения, как у for-of в JS.

Это очень плохо характеризует автора. Писать код по принципу "я так писал в языке X, если оно не упадёт с syntax error, значит должно работать точно также"… В общем логика тут немного безумная. В большинстве языков вообще будет syntax-error.

Я же говорю: дело не в динамически типизированных языках в целом, а в неочевидном синтаксисе конкретно JS.

Конструкция for-in ожидаемым образом работает в большом числе языков — кроме Python и Ruby, ещё как минимум в батниках и шелл-скриптах; и в одном лишь JS она означает нечто иное.

В том же Go, с которого "переходил" автор, не всё так однозначно. Угадайте, что выведет этот код?


arr := []int{2, 1, 0}
for x := range arr {
    fmt.Printf("%d\n", x)
}

В любом языке желательно знать синтаксис, интуиция может подвести.

Выведет ключи, что-то вроде for in в JS. Не сложно и понятно

А в JS это самое — сложно и непонятно

Вот безумная логика:
x = [1,2,3,4,5];
for (var i in x) {
console.log(x[i+1]);
}
Думаете, будет 2 3 4 5 undefined?
Это совершенно естетсвенно так думать.
Но увы.

Думаю, что i — строка. Правильно думаю?

А если с динамически типизированными языками до этого в целом неработали


А может, просто изначально не надо было делать такую херню как «динамически типизированные языки»? Ведь если тип определен в compile time, то с разбирательством что и как работает интеллектуального траходорма на порядки меньше. А программа, на чем бы не была написана — это не о «писать», это об «читать, понимать и развивать, в том числе и тем, кто ее первый раз в жизни видит».

Аргументы против динамической типизации как раз понятны. Но я думаю это топик для отдельного спора. Я тоже больше склонен к статической типизации, нежели к динамической. Но не жалею, что много лет писал именно на динамической, т.к. оказалось, что есть и свои плюсы. Не имея больших оков, и не имея нужды доказывать всё компилятору, оказывается очень удобно работать со всякими сложными абстракциями. Многие мои знакомые которые всю жизнь пишут на статически типизированных языках очень долго въезжают в некоторые абстракции, или не понимают их вовсе. Часто не понимают реальной подоплёки под многими паттернами, т.к. для них многие из них были скорее попыткой обыграть компилятор, нежели представляли решение какой-то конкретной задачи.


И вот сейчас имея Typescript со структурной типизацией и продвинутыми generic-ами я ловлю себя на том, что мне было бы физически больно писать на каком-нибудь более костном языке, где к типам прилагаются ещё и кандалы.

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


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

На самом деле я не думаю, что на этот вопрос можно дать объективный ответ. К тому же всё очень сильно зависит от вашей команды. Если писать такие абстракции которые только их автор и может поддерживать, то это одна история. Если эти абстракции, заключая в себе, много сложности, в значительной степени упрощают весь остальной код, а сами хорошо протестированы, то это другая история… И переменных в этом уравнении куда больше двух. Так или иначе они сужают bus-factor. И… вызывают привыкание.


Могу лишь сказать, что в отрыве от задач бизнеса, опираясь скорее не собственные хотелки, их писать очень приятно. Этакий challenge за challenge-ом. Люблю нетривиальные задачи. А вот рутинное копи-пейст программирование нагоняет тоску.

Этакий challenge за challenge-ом


В кровати, хате и халате
Покой находит обыватель.
А романтик все снует,
В шестеренки хер сует.

(с) Губерман
НЛО прилетело и опубликовало эту надпись здесь

У меня есть задача на Шарпах, которую я не могу решить полностью статически. Допустим, я хочу получить такой контракт:


class IListener<TEvent> {
    void On(TEvent ev);
}

class MyClass : IListener<EventA>, IListener<EventB> {

    public void On(EventA ev) {
        // EventA handler
    }

    public void On(EventB ev) {
        // EventB handler
    }

}

class Emitter {}

public class App {
    public static void Main () {
        var emitter = new Emitter();
        var myClass = new MyClass();

        emitter.Subscribe(myClass);
    }
}

Я могу написать Emitter без рефлексии, который позволит мне делать следующие вызовы:


if (condition) {
  emitter.Fire(new EventA())
} else {
  emitter.Fire(new EventB())
}

Но не могу написать следующий код:


var ev = condition
  ? new EventA()
  : new EventB();

emitter.Fire(ev);

И хорошо, потому что у класса может быть еще один метод On который вообще ни к каким интерфейсам не относится (и может даже приватный), и вызваться не должен ни при каких обстоятельствах.


В общем, это нормальная ситуация. Если нужна открытая иерархия то вы не можете гарантировать что нужный метод есть. Если закрытая, то делается АДТ и единственный метод On в котором свичом разбирается это адт.


А вообще такие вещи визиторами обычно делаются, с OnEventA/OnEventB

В общем, это нормальная ситуация.

А я и не спорю. Я вообще не представляю, как статически это реализовать. По сути оно сводится к такому:


class MyClass : IListener<EventA>, IListener<EventB> {
    public void On(EventA ev) {}
    public void On(EventB ev) {}
}

var ev = condition
  ? new EventA()
  : new EventB();

new MyClass().On(ev);

Естественно, тут нельзя выбрать статически. Сами требования к задаче — динамические.


А вообще такие вещи визиторами обычно делаются, с OnEventA/OnEventB

Понимаю. Но немного ни к чему обязывающей рефлексии — и у меня в два раза меньше кода.

Так а зачем тут рефлексия? Чем это лучше чем


class MyClass : Listener {
    public override void OnEventA (EventA ev) {}
    public override void OnEventB (EventB ev) {}
}

IEvent ev = condition
  ? new EventA()
  : new EventB();
new MyClass().On(ev);

И мне не кажется что тут есть какие-то динамические требования. Вопрос только как их выразить. Хотя шарп довольно слаб, тут не поспоришь.

А если у вас 1000 эвентов, а листенер должен слушать только 3 из них?


Плюс ваш код не работает

Оверрайдите столько методов сколько эвентом собираетесь слушать.


Плюс ваш код не работает

В каком плане? Это пример, а не полноценное приложение.

Ну я понял, что пример. Вы там метод On вызываете, которого нету… Он делает пример, увы, непонятным.


То есть Listener — такой огромный класс с тьмой методов на каждый эвент? И добавляя эвент я должен зайти в листенер и добавить ещё метод в него, правильно?

Да, так и делают в таких случаях. Необходимость написать один switch (который причем студия даже сгенерировать может) не перевешивает кучу плюсов от такого. У меня не один проект был, где люди пожидились на такой свитч, и я потом днями искал кто что создаёт, потому что через рефелкшн чето куда-то пролезает, куда-то ещё передается, и хрен что поймешь. Особенно, если проект для билда и запуска требует какой-то хитрой магии, поэтому локально не работает, и просто подебажить нельзя.

Не, у нас нету проблем. Рефлексия очень простая и всё очень надёжно и понятно. А в клиентском коде всё очень статично.

И как выглядит решение с рефлексией?

Пишу по памяти. Рефлексией создается только Invoker эвента. Что-то вроде такого класса:


class InvokerOfMySpecificEvent {
  public void TryInvoke (object listener, MySpecificEvent ev)
  {
    if (listener is IListener<MySpecificEvent> target) {
      target.On((MySpecificEvent) ev);
    }
  }
}

Теперь при Fire события ищется его инвоукер и событие вызывается на всех подписчиках.


private interface IInvoker {
  void TryInvoke (IListener listener, IEvent iEvent);
}

private class Invoker<TEvent> : IInvoker where TEvent : IEvent {
  public void TryInvoke (IListener listener, IEvent ev)
  {
    if (listener is IListener<TEvent> target) {
      target.On((TEvent) ev);
    }
  }
}

public class Emitter {
  private readonly List<IListener> listeners = new List<IListener>();

  public void Subscribe (IListener listener)
  {
    listeners.Add(listener);
  }

  public void Unsubscribe(IListener listener)
  {
    listeners.Remove(listener);
  }

  public void Fire( IEvent ev )
  {
    var invoker = GetInvoker(ev);

    foreach (var lnr in listeners) {
      invoker.TryInvoke(lnr, ev);
    }
  }

  private readonly Dictionary<Type, IInvoker> invokers = new Dictionary<Type, IInvoker>();

  private IInvoker GetInvoker (IEvent ev)
  {
    IInvoker invoker;
    var type = ev.GetType();

    if (!invokers.TryGetValue(type, out invoker)) {
      var InvokerClass = typeof(Invoker<>).MakeGenericType(type);
      var invoker = (IInvoker) Activator.CreateInstance(InvokerClass);
      invokers[type] = invoker;
    }
    return invoker;
  }
}

На самом деле у нас событиями выступают команды в движке, потому как-то так выглядит использование в логике:


class FireCommand : Command {
  public FireCommand (Unit unit)

  public void OnExec () {
    var weaponFireCmd = unit.weapon.Fire();
    commander.Exec( weaponFireCmd );
  }
}

class CannonWeapon : IWeapon {
  public CannonWeapon(int power)

  Fire() => new FireCannonCommand(power);
}

class RocketWeapon : IWeapon {
  Fire() => new FireRocketCommand();
}

class BulletWeapon : IWeapon {
  public CannonWeapon(int mass)

  Fire() => new FireBulletCommand(mass);
}

Теперь представим, что у нас есть как-то звуковик, который хочет проигрывать звуки при выстрелах.


class SoundManager
: IListener<FireCannonCommand>
, IListener<FireRockerCommand>
, IListener<FireBulletCommand> {

  void On (FireCannonCommand ev) {
    if (ev.power > 10) {
      Play("BigCannonSound.mp3");
    } else {
      Play("CannonSound.mp3");
    }
  }

  void On (FireRockerCommand ev) {
    Play("RocketSound.mp3");
  }

  void On (FireBulletCommand ev) {
    if (ev.mass > 200) {
      Play("BulletSound.mp3");
    } else {
      Play("SmallBulletSound.mp3");
    }
  }

}

Ну и где-то при инициализации саунд-менеджера:


class Installer {
  void Install () {
    // bla-bla
    emitter.Subscribe(soundManager);
  }
}

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


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


Ну и стек-трейс очень простой:


Commander:Exec
└─FireCommand:OnExec
  └─Commander:Exec
    ├─FireCannonCommand:OnExec
    └─Emitter:Fire
      └─Invoker<FireCannonCommand>:TryInvoke
        └─SoundManager:On(FireCannonCommand)

Скажите честно, понятен ли для вам стек-трейс, архитектура приложения и зависимости модулей?


Да, кстати, ещё у нас иерархия событий красиво выстраивается в логе.


Вот пример из тестов

Простите, что картинкой, но Хабра херит разметку


Каждое из этих событий может дёрнуть эмиттер, а тот — найти листенер.

Мне всё понятно, но я и говорю: это экономия в один switch, который заменен на поиск через рефлекшн. Мне это кажется шагом назад, потому что очевидность что от чего зависит понижается. У нас сразу появляются кэши и вот это всё.


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




Могу привести более приземлённый пример. Было у меня два почти одинаковых проекта (два человека в одной компании сели писать два симметричных сервиса, только один по заказам, а другой по другой сущности, но требования были очень похожи). В одном был свитч на ~50 веток который ставил в соответствие команду и действие. В другом через рефлекшн доставались все наследники ICommand и также через рефлешн определялось, что должно вызываться. Так вот, как раз с вариантом на свитче проблем было меньше всего — новые люди когда создавали по-аналогии новые команды находили свитч через find usages и добавляли ветку, и всё такое. Вариант на рефленше тоже в целом работал, но люди часто забывали или магический маркерный интерфейс унаследовать, или еще что-нибудь, и получали разлом в лучшем случае на этапе QA, а иногда и позже.




В общем, если я вижу using System.Reflection в начале файла, у меня сразу возникает определенное сомнение в необходимости подобного. И как показывает практика — часто этого можно избежать. Я проходил этот путь: сначала рефлкшн, потом кодогенерация на Expression, в последнее время, если никуда не деваться и вот не получается выразить обычными средствами, — кодогенерация через roslyn api, такие вот процедурные макросы с немного страшноватым синтаксисом. То есть, если лень писать такие свитчи руками — пусть генерирует машина. Всё лучше, чем ошибиться в рантайме.

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

Тоже был такой момент. Теперь у нас вариант со свитчем окончательно запрещён — если разрабатывается много модулей паралельно большой командой и в конец свича добавляется маппинг, то на мержах будут конфликты каждый раз. QA возвращает на программиста, тому приходится править коммит. Потеря времени и, что хуже — бессмысленное раздражение. Я уж молчу про моменты, когда свитч забывается.


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


У нас так карточки создаются. Свитч в итоге проклинала вся команда, в т.ч. программисты и гейм-дизайнеры.

Тоже был такой момент. Теперь у нас вариант со свитчем окончательно запрещён — если разрабатывается много модулей паралельно большой командой и в конец свича добавляется маппинг, то на мержах будут конфликты каждый раз. QA возвращает на программиста, тому приходится править коммит. Потеря времени и, что хуже — бессмысленное раздражение. Я уж молчу про моменты, когда свитч забывается

Странно, у нас гит догадывается помёржить всё как нужно, не раз проверяли.


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

А вот тут у нас всё же умудрялись косячить + обучающий момент пропадает, человек конечно может догадаться, что там маркерный интерфейс, пойти посмотреть как он используется, и догадаться что к чему, но чаще просит конфлюенс где будет написать "а у нас на проекте вот этот магический интерфейс за это отвечает, а то — за то. Чтобы добавить такой-то функционал скопируйте тот NewCommandExample.cs и отредактируйте как нужно".


У нас так карточки создаются. Свитч в итоге проклинала вся команда, в т.ч. программисты и гейм-дизайнеры.

Забавно, что вы приняли противоположенное нашему решение. Не могу сказать, вопрос в культуре разработки, философии о назначении типизации (мне ближк идеология parse, don't validate) или просто размер команды.

Кстати, у меня вопрос, а почему не сделать что-то в духе:


class SoundManager : IListener {
  void On(IEvent event) {
    switch (event) {
      case FireCannonCommand ev:
        if (ev.power > 10) {
          Play("BigCannonSound.mp3");
        } else {
          Play("CannonSound.mp3");
        }
        break;
      case FireRockerCommand ev:
          Play("RocketSound.mp3");
        break;
      case FireBulletCommand ev:
        if (ev.mass > 200) {
          Play("BulletSound.mp3");
        } else {
          Play("SmallBulletSound.mp3");
        }
        break;
    }
  }
}

По сути у вас АДТ в IListener, ну и вы обрабатываете те что интересно.

Вот так можно, конечно. Но мы, по сути, эту логику вынесли в Emitter. Не люблю ни свичи, ни большие методы. Да и декларативность уже не та. И рефакторить легче. Стек Трейс поинтереснее. Тут уже вкусовщина начинается, а рефлексия всё-равно скрыта глубоко и на коде кроме Эмиттера никак не сказывается.


Вы, кстати, не ответили:


Скажите честно, понятен ли для вам стек-трейс, архитектура приложения и зависимости модулей?

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


public class IEvent {
  void Notify (Emitter emitter);
}

public class Emitter
{
  private readonly List<IListener> listeners
    = new List<IListener>();

  public void Subscribe (IListener listener)
  {
    listeners.Add(listener);
  }

  public void Unsubscribe(IListener listener)
  {
    listeners.Remove(listener);
  }

  public void Fire<TEvent>( TEvent ev )
    where TEvent : IEvent
  {
    foreach (var lnr in listeners) {
      if (lnr is IListener<TEvent>) {
        lnr.On(ev);
      }
    }
  }
}

public class Commander
{
  // blabla

  void Exec (ICommand cmd) {
    cmd.OnExec();
    cmd.Notify(emitter);
  }
}

interface ICommand : IEvent {
  void OnExec();
}

public class MoveCommand : ICommand
{
  public void OnExec () {
    // bla-bla
  }

  public void Notify (Emitter em)
    => em.Fire(this);
}

public class AttackCommand : ICommand
{
  public void OnExec () {
    // bla-bla
  }

  public void Notify (Emitter em)
    => em.Fire(this);
}

Но, были причины, почему оставили рефлексию.

Вот так можно, конечно. Но мы, по сути, эту логику вынесли в Emitter. Не люблю ни свичи, ни большие методы. Да и декларативность уже не та. И рефакторить легче. Стек Трейс поинтереснее. Тут уже вкусовщина начинается, а рефлексия всё-равно скрыта глубоко и на коде кроме Эмиттера никак не сказывается.

Ну, с этим я могу согласиться.


Вы, кстати, не ответили:

Ответил, смотрите первое предложение: https://habr.com/ru/company/ruvds/blog/499670/#comment_21567940


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

Тоже неплохой вариант, и ничуть не менее декларативный, особенно если чутка переписать:


foreach (var lnr in listeners.OfType<Listener<TEvent>>()) {
  lnr.On(ev);
}

Далеко не любой язык со статической типизацией умеет в high order rank polymorphism, или алгебру типов. В свою очередь, чтобы обойти ограничения ты или cast-ишь (убегая от типов), либо копипастишь (или даже кодогенерируешь), или используешь рефлексию (ох...), или просто не используешь какие-то прикольные фишки. Чаще даже не догадываешься об их существовании, т.к. в материалах про твой язык тебе они не попадались, в коде библиотек с которыми ты работаешь — ничего подобного нет, а всякие там книжки про паттерны проектирования выглядят как-то очень надуманно.


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


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


Ну ты понял. Просто представь что ты 10 лет писал на Idris-е, а потом тебя посадили на pascal образца 90-х.

НЛО прилетело и опубликовало эту надпись здесь
Просто не надо о ней судить по тем языкам, где с выразительностью языка типов всё плохо

  • Java
  • Kotlin
  • Go
  • C#
  • Swift
  • Rust
  • C
  • C++

Ну вот я взял из списка самых популярных языков в 2020г (первая ссылка в гугле) те что со статической типизацией. Ни Scala ни Haskell тут нет. Вопрос в каких из этих языков есть типовая алгебра, сужение типов в if-ах и condition-ах, полиморфизм высшего порядка?


Ну так из этого ведь не следует, что статическая типизация плохая

С этим соглашусь, да.

НЛО прилетело и опубликовало эту надпись здесь

Если честно, то я не смог распарсить второй абзац :)
Но на всякий случай повторю — я не против статически типизированных языков (я за). Но я бы предпочёл динамически-типизированный язык тем из них, которые имеют слабую недоразвитую систему типов. Вотъ.

НЛО прилетело и опубликовало эту надпись здесь
Если честно, то я не смог распарсить второй абзац :)

Я так понял 0xd34df00d говорит, что у вас есть шанс при старте проекта выбрать любой язык и ваш выбор никак прямо не завязан на их долю на рынке.

О, спасибо за перевод. Нет, это не так. К примеру у нас на backend-е scala. И вроде как по итогу выбор скалы рассматривается как ошибка, т.к. очень сложно найти и нанять scala-разработчика. И не из любого java разработчика удаётся сделать scala-разработчика. Техлид говорит, что если бы он вернулся назад во времени то выбрал бы какой-нибудь go или java.


Точно так же и я не могу взять для нового проекта какой-нибудь elm, потому что с точки зрения бизнеса это будет фиаско. Мы с огромным трудом даже javascript разработчиков находим, а на elm не найдём никогда.

НЛО прилетело и опубликовало эту надпись здесь

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


Скажем моим хотелкам удовлетворяют всего 3-4 города в Европе. Мюнхен и города Швейцарии. Будь я каким-нибудь Haskell программистом, насколько сократился бы мой выбор? :) А JS/TS разработчики нужны в любой дыре. И работы им лет на 20 вперёд заготовлено.


Думаю у вас в США с этим получше (как и с IT в целом). Но тут уже сама страна и её бюрократические препоны вносят немалый вклад...

Осталось понять, что лучше — хаскель-разработка в Москве или тс-разработка в Мюнхене )

К примеру в моей конторе пишут на Objective C, на Swift, на Kotlin и на Scala. Даже Scala (2-ая) не умеет того, что умеет TypeScript.

Я как раз недавно писал хабрапост про артефактные цвета в CGA, и мне нужно было сгенерировать таблицу возможных цветов и соответствующих им композитных сигналов.
Эта программа, скорее всего, никогда больше никому не понадобится.
По-вашему, я её всё равно должен был писать на языке, позволяющем «читать, понимать и развивать, в том числе и тем, кто ее первый раз в жизни видит»?
Да для себя можно писать на чем угодно равно как и заниматься любым извратом. Речь идет о коллегах, нынешних и потенциально будущих, а не о мастерах по созданию произведений write&dump.
То, что я писал генератор для таблицы цветов на языке с динамической типизацией — это изврат?
Повторюсь — я писал про промышленное производство, а не про поделки для себя. Для себя можно хоть на заборе, хоть на brainfuck.
НЛО прилетело и опубликовало эту надпись здесь
for...in — это самый удобный перебор в js.
У .foreach и for слишком много неудобств и ограничений.

Простите, а вы точно знаете чем отличается for-of, for-in, for;;, forEach? и знаете про методы .entries, .keys, values?

Здесь в js плохо то, что если работаешь с массивом, в котором элементы пронумерованы от 0 до N, и по-другомы быть никак не может, то ожидаешь что эта прогрессия будет иметь числовое выражение, а не строковое.

В JS ключи бывают только строковыми. То, что вы можете искать и по цифрам — это динамическая природа языка. Это отвечает на много вопросов)

Это нормальное поведение тех же перловых регекспов со /g (привет PCRE):
$ perl -le 'my $a = q{abcabdabe}; print $1 while ( $a =~ /(ab.)/g );'
abc
abd
abe

Без /g этого не происходит.
s/нормальное/традиционное/
цирк в том, что регэксп это на самом деле объект с состоянием. а /a/g это короткая запись его конструктора.
А если не хочется беспокоиться что там у нас в состоянии находится нужно просто создать новый регэксп.
НЛО прилетело и опубликовало эту надпись здесь
Ну да, сделано не очень понятно. Но это хорошо известный прикол и в документации упоминается.
вызванный несколько раз на одном и том же экземпляре глобального регулярного выражения, будет начинать проверку с конца предыдущего сопоставления.


Обычно, чтобы не следить за этим, используют методы строки, куда передается регэксп, т.е. String#match и String#search.
Заметьте, что в данном примере мы как раз вызываем методы самого объекта регэксп.

Любой парсинг требует возможность продолжения с точки последней остановки.

В моём понимании регэксп это детерминированный тест, который всегда возвращает одно и то же,

'somestring'.match(/someregexp/)

Вот так вот используют RegExp-ы в JS практически всегда. И да, оно работает именно так, как вы описали. А то что в статье, это… ну такой более низкоуровневый примитив для хитрых нужд.

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

Простите, но я не увидел ответ на вопрос. Логично, что если Вы совершаете манипуляции с числами в рамках одной системы — проблем с Endian не будет. Но если это массив данных с другого устройства, где архитектура работает на BigEndian, то у Вас очевидно будут проблемы. Дело в том, что Вы не всегда определяете архитектуру системы. Например, MIPS архитектура полностью построена на BE и является практически стандартом на российском рынке импортозамещения. На мой взгляд, Вы не закончили мысль.
Предполагается что ответ «А хрен его знает» :)
Например, MIPS архитектура полностью построена на BE и является практически стандартом на российском рынке импортозамещения.

MIPS может работать в LE/BE режимах.

К примеру PS2 — LE, IRIX — BE.
Байкал — T1 использует LE.
Из всего этого списка нежданичков пример
const x={
  i: 1,
  toString: function(){
    return this.i++;
  }
}

if(x==1 && x==2 && x==3){
  document.write("This will be printed!")
}

совсем несправедливый.

Если в языке можно перегружать какие-то операторы, то очевидно, что там можно получить дичь. В питоне тоже можно сделать
class Foo:
    def __eq__(a, b):
        return random() < 0.95 or os.system('rm -rf /')

И в плюсах. Да где угодно.
Тут как бы не оператор сравнения переопределили а преобразования к строке. Ни в питоне ни в С++ такой оператор не влияет на сравнение.
НЛО прилетело и опубликовало эту надпись здесь

Справедливости ради, если не определен метод __hash__, то именно преопределенный __repr__ или __str__ и вызовется при попадании в словарь. Или, если не ошибаюсь, при сравнении с ключом словаря. Надо проверить, как у компа буду

Ну так если вы возьмёте === вместо == (как собственно и пишут на этом языке), то у вас не получится никаких преобразований к строке и прочих странностей. В большинстве крупных проектов на уровне линтеров запрещён ==.


Поясню, js-код выше это непонятное нечто. У нас есть объект, в нём есть одно поле. И мы зачем-то нестрого сравниваем этот объект с числом. Приведение к числу мы не описали, поэтому система сваливается до сравнения строк. Приведение к строке мы написали, причём таким образом, как будто хотели устроить диверсию. Ну… диверсия получилась.


Однако в воздухе витает вопрос, а на черта это всё было сделано? Аналогия с переопределением __eq__ более чем корректна. В JS просто нет __eq__, а есть пара других методов.

Начали вроде весело, а продолжили очередными сравнениями. Да, давайте в 20ый раз посмеемся


var a = "41"
a += 1
// 411, неправильно, несбалансированно, странно.
var b = "41"
b -=- 1
// 42

АХАХХАХАХАХА.

Perfectly balanced. As all things should be. Ну а вообще, обьединить Таноса и ответ на «Главный вопрос жизни, вселенной и всего такого» — это мощно.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Кривость одной технологии действительно исправляется кривизной другой?
НЛО прилетело и опубликовало эту надпись здесь
Почему любая критика любимой технологии воспринимается как «нападки»? Любой человек меняя один язык на другой руководствуется в первую очередь своим багажом опыта и сравнивает. Находя или не находя известные паттерны учиться проще. Вполне естественно, что «норм» для варящихся в своём соку джаваскриптеров может оказаться «не норм» для гофера-свитчера. Так и наоборот! Но бестолковые для технического спора выпады в стиле: "… надо быть уверенным, что сам не без греха." и прочие переходы на личности в комментариях вокруг явно показывают, что люди в самом деле оскорбляются, принимая критику на свой счет.
Что заставляет людей настолько прирастать душой к инструментам, делая из них субкультуру? Боязнь разочарования в собственном выборе?
Это что, вон в питоне взяли на 3ю версию тупо заменили все strings на utf8 и сказали пляшите как хотите.
Как раз тот случай когда ошибку изначального дизайна исправили, хоть и ценой поломки совместимости.

Чушь. Ничего не исправили они. Проблем с этим не было изначально ибо был unicode(). Просто кто-то сказал "давайте будем прогрессивными" и сделали utf8 дефолтом. Даже не думая, что во всех других системных языках utf отдельно идёт (что правильно ибо подсистемы все (сеть, диски, память) работают в C локале.).


В общем разом убили простоту питона в угоду новомодью. При этом проблемы с тредингом до сих пор не решают чтобы "не ломать компатабилити". Где логика? В моем компе 8 ядер, ему через год 10 лет будет! Это уже не смешно.

Строки и массивы байт семантически отличаются и их смешивать в один тип не совсем правильно (например аналог в Qt — QString и QByteArray). Преобразование между строками и байтами простое, не вижу в чем проблема. С python3 куда логичнее стало.

GIL — проблема не в стандарте языка а конкретной реализации, хоть и основной. Делать thread safe решения ударило по производительности однопоточной версии. Неприятно конечно, но тут не причина в совместимости, разве что возможно частично на уровне C API.
Строки и массивы байт семантически отличаются и их смешивать в один тип не совсем правильно (например аналог в Qt — QString и QByteArray). Преобразование между строками и байтами простое, не вижу в чем проблема. С python3 куда логичнее стало.

Qt не язык программирования, а GUI система. Если бы это было в Tk проблем не было бы.

Так выходит чтобы написать простой скрипт аля dd или tcpdump на питоне придется танцевать вокруг урезанных (по сравнению с str) bytes и bytearray. Для вывода в print данных придется делать encoding и т.д. и т.п.

Простота решения была очевидна — оставить str таким же как в других языках и сформировать новую unicode string для конкретной работы с unicode (который как выяснилось в итоге имеет свои лимиты, так что ждем python4 c ucs или utf32). И ее укороченную версию u"".

Грубо говоря качественное изменение было абсолютно негативным.

Касательно GIL — да проблема не в стандарте. Но факт что решение не было проведено в течение !20! лет говорит о том, что это не приоритет.

В итоге Python3 только недавно (5 лет) начал набирать популярность, почти уверен это связано с JS default utf16 и просто тупо дропом Python 2.7 поддержки.

Python3 тупик, который только и ждет какой-то качественной замены языком с таким же читабельным синтаксисом и поддержкой трединга. Возможно форк python2.
> Qt не язык программирования, а GUI система.
Строго говоря это «с++ фреймворк». Можно и консольные тулзы писать )

> с таким же читабельным синтаксисом
На вкус и цвет, а по мне
",".join(arr)
дичь полнейшая.
Почему не
arr.join(",")
?
Ну и прочее подобное (хорошо запомнилось только это).
Golang в разы читабельней и меньше «правил чтения» надо запоминать
Golang в разы читабельней
Это скорее маргинальное мнение (субъективность читаемости оставляет мало места метрикам). По-моему, Python хвалят за читаемость, а Go критикуют за многословность из-за бедного синтаксиса, то есть наоборот. Обилие синтаксиса не особо коррелирует с читаемостью, чаще наоборот. Brainfuck как раз основан на минимализме правил. Из менее радикальных примеров, в Lisp минимум правил и в Forth без надстроек. Тоже часто критикуемы за нечитаемость.
Почему не arr.join(",")
Выбор явно намеренный, видимо, автору это показалось более похоже на естественный английский. Читайте как "," joins array. Это, можно сказать, идеология Python. Могу согласиться, что это сомнительная цель и сомнительное решение с т.з. Computer Science.
Дело не в естественном языке: string.join принимает аргументом любой iterable.
Если бы делали наоборот, то пришлось бы определять методы join у списков, кортежей, множеств, генераторов и чего угодно ещё.
Только iterable объектов, сериализующихся в строку. Да, это было ошибочное предположение из одного из плюсов Python. Работа с Iterable[str] выглядит самым логичным выбором.
Я ещё, пожалуй, добавлю:
Обилие синтаксиса не особо коррелирует с читаемостью, чаще наоборот. Brainfuck как раз основан на минимализме правил. Из менее радикальных примеров, в Lisp минимум правил и в Forth без надстроек. Тоже часто критикуемы за нечитаемость.
Языки с избыточно богатым синтаксисом, например Perl, так же часто критикуемы за нечитаемость.
НЛО прилетело и опубликовало эту надпись здесь
Даже не думая, что во всех других системных языках utf отдельно идёт

Почему вы сравниваете Python с системными языками, а не с другими скриптовыми, вроде того же JS?
В Time смешали две совершенно, абсолютно несвязанные сущности – wallclock time, и стабильную отсечку временных интервалов, таймер.

Не очень понятно, о чем именно речь и в чем проблема.

Например неизвестно, как оно себя ведет, когда ntp например ускоряет или замедляет часы.

Ускоряет или замедляет? Каким образом что-то должно вообще на NTP реагировать? Если оно на монотонных часах реализовано, как это корректно делается и сделано в Go, то никак не реагирует. Wallclock и NTP никак не касается этого всего. Хотя конечно даже монотонные часы имеют свои причуды в зависимости от ОС.

Таких статей уже сотни, если не тысячи. В целом, все так и есть, поэтому на проекте от 100 строк кода нужен линтер, он нескольких тысяч строк — typescript.

Javascript мой любимый язык программирования… когда нет работы и проектов я только на нем и пишу…

Я понимаю, что статья саркастическая, но правильным аналогом кода


typeof([1][0])

Является код


for(let i of [1]){console.log(typeof(i))}

Который выдаёт number.

Ну то что человек не смог подобрать «правильный» аналог, тоже не в пользу языка говорит.
Если вещь можно сделать несколькими разными способами часто будут использовать самый неправильный )
К тому же не все (кто нибудь ?) следят за нововведениями у языков на которых не пишут в основном.

ЗЫ. Сам от ES6 был в восторге после многолетнего опыта с callback hell и прочим for...in...hasOwnProperty

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

Если лень прочитать пару предложений

Перерыв гору текста, туториалов, спецификаций (а где они? ага MDN раньше не было) и прочего )
Не забываем, человек не профильный в js.

Не было MDN — это когда? По-моему, он с нами уже лет 10. Все доки есть там.


А доки node.js прямо на оф сайте.

Я пересел с автомобиля на горные лыжи, и вот почему(с)

2020 год, люди до сих пор не отличают язык программирования отличают от рантайма
А уж блок где автор ругается на (x, y, z) == (...) вообще вызвал смех. Как можно жаловаться на скобочки в языке, основанном на лиспе?

Но ведь это выглядит как список/кортеж, а не progn.

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


Всё субъективно…

Традиция записывать кортежи с круглыми скобками — намного старше JS, да и программирования вообще.

Традиция записывать возведение в степень с помощью ^ — намного старше JS, да и программирования вообще.

JavaScript основан на Scheme, который основан на Lisp…
И квадратные скобки для списка только в Haskell, а в других языках и синтаксиса для списков нету
Массив это не список если что )

Сам пишу на го, но из-за аспектов (нет наследования, дженериков, явных интерфейсов, ещё что-то xD), которые заставляют писать один и тот же код несколько раз за небольшой объем кода (на 100 строчек кода из них будет около 30% (если не больше) воды (повторений, которых можно было бы избежать)), я начал смотреть тайпскрипт. Сейчас использую гошку только для реализации небольших программ (не более нескольких сотен строк, и то, если необходима именно скорость), а для чего-то побольше использую js (typescript). К тому же, использовать воркеров в ноде гораздо безопаснее, чем использовать горутины в го, имхо.

Наследование можно заменить композицией.

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

Вы считаете, что композиция может всегда заменить наследование?
А разве нет?

Эмм, нет. Для того, чтобы это понять достаточно знать определения (зависит от логики человека, который читает определения). Знать где и что используется. Иметь опыт использования...

И в каком же случае композиция не может заменить наследование ?


type Obj struct {
  p: ParentObj
}
func New() *obj {
  return &Obj{
    p: ParentNew()
  }
}

func (o *obj) IWantToInherit() {
  o.p.ParentFunc()
  // some other logiс
}

не ?


Согласен, что то писать придется, но точно не 100500 строк или 30%

И в каком же случае композиция не может заменить наследование ?

А ну только если в качестве типа входных/выходных хочется указать базовый класс. Но для этого есть интерфейсы.

Но тут вступают в бой неявные интерфейсы… Благодаря которым появляется множество неочивидных мест, которые не может отловить компилятор…
Да даже когда ты хочешь использовать обычный DI, тебе приходится писать очень много различных фабрик, которые можно было бы избежать в том же js.

Но тут вступают в бой неявные интерфейсы…

Да блядство — делайте явные. Вас гитлер заставляет неявные делать, чтобы хрен кто разобрался ?

В го есть явные интерфейсы?

Понял про что вы. Был не прав )
В целом у народа не возникает с этим проблем, значит наверное все норм )
Принцип "не сри в свой код" мне в принципе нравится )

Да даже когда ты хочешь использовать обычный DI, тебе приходится писать очень много различных фабрик, которые можно было бы избежать в том же js.

Ну это просто не знание языка по сути ) Все легко и просто решается.

Очень абстрактный ответ. Жаль, что го в абстракции не силён...

Ну это просто не знание языка по сути ) Все легко и просто решается.

Ну давайте, порадуйте нас своим знанием языка

Ну наверное вам просто не нужен DI при решении задачи на go (обычно так и бывает). Не нуж0н очередной фреймворк. Написал тулзу и забыл. Микросервис и все такое. Пакет utils никто не мешает соорудить (но скорей всего от него откажетесь потом)
Честно говоря я считаю тех кто использует прототипированное наследование в js извращенцами.
Написал гору (не повторяющегося) кода без наследования и ниче )

Ну так покажите. Где же?


Написал гору кода без наследования и ниче )

При чём тут наследование? Вы знаете, что такое DI?

Ну так покажите. Где же?

Сорри не могу.


Вы знаете, что такое DI?

Инжекция зависимостей. Ну честно ни разу о ней не думал когда писал на go. И неплохой код получался...

Дык я и говорю про то, что на го удобно писать мелкие программы, а в них нет особого смысла использовать DI.
Чем же вам так насолило прототипное программирование?

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

Что по вашему "не мелко" — что то с GUI? Тут да, go сосет.


Чем же вам так насолило прототипное программирование?

Полной не логичностью. Еще скажите что сахар типа class просто так ввели )


ЗЫ. Докер, кубер — это все мелко ?

"Не мелко" — либо объем кода либо кол-во интфейсов/классов. Вот у меня есть проект на TS, где 50+ классов/интерфейсов, строк кода несколько тысяч или больше (сомневаюсь, что больше 10к, я не веду учет), и я не представляю как это бы все смотрелось на Го. Скорее всего на го кода было бы в полтора раза больше.
Насчет прототипного программирования вы говорите слишком абстрактно, возможно, вы в этом не разбирались

"Не мелко" — либо объем кода либо кол-во интфейсов/классов

Ну вот есть докер с кубером. Понимаю что там может быть написано все "вопреки", но сомневаюсь.


Насчет прототипного программирования вы говорите слишком абстрактно, возможно, вы в этом не разбирались

Именно! Я не считаю нужным разбираться в мутных вещах )
А тем более использовать их.


ЗЫ. String.prototype.OLOLO и ололо? )

Мдя, без желания узнать как и что устроено хорошим разработчиком трудно стать.
А вы видели исходники докера/кубера? Или это опять просто популистические высказывания?

Мдя, без желания узнать как и что устроено хорошим разработчиком трудно стать.

Извиняюсь но не в случае с js. Там я знаю то что мне нужно (не используй колбеки, опасайся for...in и тому подобное. Не используй наследование :)).


А вы видели исходники докера/кубера?

Нет, но давайте с вами разберем, если хотите. Просто не считаю это "мелкой" программой вот и все.


А если вы про НАЛИЧИЕ исходников — все на гитхабе есть.


Извиняюсь но не в случае с js. Там я знаю то что мне нужно (не используй колбеки, опасайся for...in и тому подобное. Не используй наследование :)).

Эту мысль можно было бы развернуть, но я не буду )

А вы знали, что суффикс "сь" указывает на говорящего? =) Т.е. вы извиняете себя =)
Что-то думать (не считать "мелкой" программой) и приводить в качестве аргумента — не одно и то же

А вы знали, что суффикс "сь" указывает на говорящего? =) Т.е. вы извиняете себя =)

А вы знали что обычно до букв докапываются те, кому больше нечего сказать ?


Что-то думать (не считать "мелкой" программой) и приводить в качестве аргумента — не одно и то же

Тут вообще какая то дич не понятная. Что хотели донести? Что я Хуй ?

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

Друже, в "тонкостях" я разбираюсь. И эти тонкости мне не всегда нра. О том и речь. А ты походу выпил уже (как и я :))

Если обидел, извини.
Но меня часто выбешивает использование чего-либо в популистическом смысле (когда не разобрался, а говоришь лишь бы украсить высказывание).
Не, у меня 5 час утра

Если обидел, извини.

Обычно меня можно обидеть, если сказать что я в чем то не шарю ) Ну впринципе ты так и сказал, но я не зол )
Я никогда себе не позволяю что то сказать о том в чем не разбираюсь )

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

С учётом того, что в мире много всего, чего вы можете не знать, обидеть вас довольно легко =)

Не правильно высказался. Я имел ввиду "кто то скажет что я не прав в своем высказывании", а я обычно знаю о чем говорю ) Но не стыжусь признавать ошибки конечно )


В том, какие трудности были, в том как это работает изнутри.

Мне кажется это не было связано с языком. Но могу ошибаться конечно )

А методы можно заменить процедурами.

Вот именно!

Из-за меток. Да в JS есть метки. И в некоторых сложных ситуациях возникает неоднозначность для парсера — тут блок кода с метрой внутри или тут объект с полем внутри. Метками никто не пользуется, но ввиду обратной совместимости они всё ещё поддерживаются.

&& или || в js просто выполняет выражения по порядку в зависимости от результата предыдущего и возвращает результат в зависимости от условия
Вспоминаем например установку дефолта
val = val || 'I AM DEFAULT'


В случае с фигурными скобками просто парсер ломается, оберните в скобки и вуоля.
({property: «value»}) && {property: «value»}
Тоже самое с IIFE

function(){
  console.log('SYNTAX ERROR')
}()

(function(){
  console.log('THIS IS FINE')
})()

При этом


!function () {
  console.log('fine')
}()

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

Ну не хотел в причуды парсера впадать ) А по сути это "обман парсера" (чудный js)


Я бы такое не использовал. Используйте только то что понимаете как работает.

О каком обмане идет речь?


Правило-то очень простое — по дефолту функция понимается как FD. FD это Function Declaration, на который, например, применяется hoisting. Так вот по скольку это "declaration" — это является стейтментом. Стейтмент это управляющая конструкция — такая, например, как for/if/throw и так далее. А вот Function Expression это нечто совсем другое. И для того, что из FD сделать FE — надо явно задать контекст — то есть например использовать какой-либо оператор (оператор применяется только и только на expression'ах — поэтому функция понимается как expression и еррора нет).


А уж что вы используете за оператор — () или! или любой другой — не имеет никакой разницы.

Правило-то очень простое ....

Вы правда думаете это проще запомнить чем "парсер глючит на фигурных скобках" ?


О каком обмане идет речь?

Элементарном. Он там че то не может переварить, дай ему переваривариваюмую пищу — не обман?
IIFE это по сути в js изобретено было кстати ) Обманом парсера )

Я к тому что всякие уловки привели к появлению методик. Но я могу ошибаться. Но обычно "если нельзя, посмотрим как сделать"

Вы правда думаете это проще запомнить чем "парсер глючит на фигурных скобках" ?

От того что ваше утверждение запомнить действительно проще, правдивым оно не становится. Я выше описал, что это ввиду наличия в языке labels:. Ничего парсер не "глючит". И никто парсер не обманывает, ему просто дают больше контекста.

Парсер работает не так как ты думаешь — он глючит
Почему глючит — да вообще пох
Как то так.


Мне хватает и в самом языке ньюансов чтобы еще с парсером разбираться.

От того что ваше утверждение запомнить действительно проще, правдивым оно не становится. Я выше описал, что это ввиду наличия в языке labels:. Ничего парсер не "глючит". И никто парсер не обманывает, ему просто дают больше контекста.

Попробуйте вот с этим решить РЕАЛЬНУЮ задачу.

Лично я не сторонник goto. Моя б воля я бы убрал goto из большинства языков. Но в большинстве старых языков goto есть. JavaScript не исключение.

Причем тут goto? И да в ядре линукс используется )
Вы понимаете, что дичь несете порой ?

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

Вы понимаете что вы зациклены на метках?
Я знаю как они определяются. И вы заметили слово и двоеточие…
Это НЕ НОРМАЛЬНО.

Он маньяк

Я вполне себе понимаю как это работает поэтому и использую) Интересное оно из-за того что всего один знак а концепция функции меняется на корню.


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

Стёб стёбом, но JS прекрасно выполняет те задачи которые на него возложены.
При желании всегда можно найти за что зацепится, но в мэйнстрим все эти сложности просто не существуют. Что да существует это программисты которые пытаются съесть суп вилкой… но это уже не проблема конкретного языка
Не в контексте Go, наверное, но в целом есть замечательная фраза:
«Есть языки, которые все ругают, а есть те, на которых никто не пишет».
А вообще, современный JS на мой скромный взгляд, — это уже своего рода «ассемблер» на фоне целой кучи языков и диалектов со статическим анализом и вот этим вот всем, которые в него транслируются, если это нужно.

Несколько лет использовал Node.js. В последнее время выбираю альтернативы: Python, Erlang (Go как-то не по душе). В том числе и по тем же причинам, что описаны в статье.

Go крут, попробуй. У python дикий синтаксис, который питонщики называют легкочитаемым, а преимуществ перед go не наблюдается. Про erlang ничего не могу сказать, но изучать язык с его популярностью я бы не стал. Из всего что я юзал написаного на erlang — couchdb.

Для сравнения подходов сформулирую простую задачку: дан длинный список URL, нужно по всем ним пройтись и загрузить данные. При этом 1) это должно быть эффективно; 2) в случае сбоев загрузки, повторять попытки до достижения успеха. Знаю, как такое делать на Node.js, скрипт получается сложный и для разработки, и для чтения, но работает хорошо. Соответственно: насколько просто решить такую задачку на Go, Python, Erlang, (write here what you want)?

2) в случае сбоев загрузки, повторять попытки до достижения успеха.

А если внезапно стало какой нибудь 404?
Но это к сути вопроса дела не имеет. В целом golang для меня как заменитель nodejs, поэтому посоветую его.
Для решения вашей задачи всего лишь нужна функция парсинга которую надо запускать как горутину (типа go parse(url)), возможно перезапускающая саму себя и механизм типа sync.WaitGroup


ЗЫ. Для меня golang как очень хорошая замена nodejs. Принципы очень похожи, а строгая типизация не дает развалится коду когда решишь поменять что нибудь в каком нибудь json )

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

Наверное есть смысл посмотреть на Go. Просто сейчас уже поизучал Python, а Erlang в процессе. Можно даже на примере такой задачки сделать сравнение нескольких языков. :)

Принципы очень похожи, а строгая типизация не дает развалится коду когда решишь поменять что нибудь в каком нибудь json )

Тогда уж лучше TypeScript — типизация значительно строже, чем в Го.

Бла бла бла. Мне не нужна "СТРОЖАЙШАЯ ТИПИЗАЦИЯ В МИРЕ". Мне нужно всего лишь чтобы при смене структуры json конфига (например), весь код не развалился.
И да, вы Врун. что значит "значительно строже" ?

В гоу всё интерфейсами перетыкано. Про какую типизацию может быть речь?

Чем интерфейсы мешают типизации? Не смогли имплементировать метод или почему такая неприязнь ?


Ни разу не испытывал проблем с интерфейсами "гоу". Может они не понятны по началу, но зато удобны. В том же пхп (да хоть в с++) я вообще хз зачем нужны интерфейсы.


Ох, если вы про приведение типа из интерфейса — то это ПОЛУГАВНОКОД считай.А если ваш проект обмазан интерфейсами (мой нет), то это проблема проекта как мне кажется. Запустили гору ООПэшников, что еще ожидали ?

Ох, если вы про приведение типа из интерфейса — то это ПОЛУГАВНОКОД считай.А если ваш проект обмазан интерфейсами (мой нет), то это проблема проекта как мне кажется. Запустили гору ООПэшников, что еще ожидали ?

Этот ПОЛУГАВНОКОД™ это официально благославленный (и единственный) способ писать обобщённый код. Есть ещё один:


image


Но библиотечный код так не написать.

Мой "редактор кода" так умеет и что? ) И кстати ни разу не пользовался этой возможностью.


Но библиотечный код так не написать.

Ну значит Вам не нужно писать библиотечный код.

На вашем месте, я бы нашел все "приведения типов" и хуярил бы по голове книжкой (но это мечты).
Вобщем избавьтесь от не нужных интерфейсов

Знаю, как такое делать на Node.js, скрипт получается сложный и для разработки, и для чтения, но работает хорошо.
А можно подробнее, какие недостатки в вашем случае у тупого решения «в лоб» типа такого?
async () => {
    const urls = ['http://example.com/movies.json', 'http://example.com/songs.json'];
    const fetchUrl = async url => {
        try {
            const response = await fetch(url);
            if (response.ok) return response.json();
        } catch (_) { }
        return fetchUrl(url);
    }
    return await Promise.all(urls.map(fetchUrl));
}
(код браузерный, не нодовый)

По мне этот код сложен и в чтении, и в написании. Хотелось бы для подобного рода задач иметь инструмент более простой. Поэтому и отправился на поиски такового. :)


(В Node.js встроенный request не Promise-фицирован, что дополнительно доставляет.)

По мне этот код сложен … в написании.
Ну, не знаю, я минут 7 потратил, наверное. Из них 1 на удаление TypeScript'овых типов (писал на TS, но мы же про JS), 2 на придумывание имён переменным, и 2 на гугление синтаксиса fetch на MDN (давно руками не дёргал).
(В Node.js встроенный request не Promise-фицирован, что дополнительно доставляет.)
Ну, уж под это наверняка уже 101 разный NPM-пакет есть =)

Да, есть. Я использовал request-promise-native. Но хотелось бы, чтобы Promise-фикация была из коробки.

встроенный request это что?
да, и в ноде есть util.promisify, довольно удобный

1) request — функция из стандартного модуля https: https://nodejs.org/dist/latest-v12.x/docs/api/https.html
2) util.promisify — не знал, честно говоря; ну это прекрасно, раз можно Promise-фицировать стандартными средствами. https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_promisify_original

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

Смотрю у axios есть дополнение retry. Это хорошо.

Интересно было бы взглянуть на ваш более легко читаемый вариант на любом языке, просто тут фактически 1 строчка кода и рекурсивный бесконечный фетч которого естественно нет в стандартной библиотеке потому, что такое использование никогда не предполагается.
document.all
// HTMLAllCollection(359) [html.inited, head, …]
document.all == true
// false
document.all == false
// false
Всё ещё смешнее.
Тут мы видим, что document.all ведёт себя как NaN.
Проверяем:
isNaN( document.all )
// true
Убедились.
Но: тут мы вспоминаем, что isNaN у нас две.
Проверяем ещё раз:
window.isNaN( document.all )
// true
Number.isNaN( document.all );
// false

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

Ага, а все потому — что это ни разу не NaN, а простой объект. Разность результатов обьясняется очень просто — это обыкновенный геттер. К тому же выпиленный из стандартов (очередной вопрос к автору — зачем обсуждать что-то, что давно уже официально убрано из даже не языка, а DOM API).


А про глобальный isNaN и Number.isNaN — тоже все очень просто. Глобальный депрекейтед, так как придуман уже милиард лет назад и имеет один косяк — он старается аргумент сначала привести к намберу — и только потом сравнивает.


Естесственно с взрослением языка никто этот глобальный isNaN не использует, кроме как таких "программистов", как автор. Есть вполне давно достпный добропорядочный Number.isNaN который максимально правильно и прозрачно проверяет нан это или нет. И он нам, в принципе, правильно и говорит — это ни разу не NaN.

Но ведь object Is Not A Number )
Переименовали бы тогда уж в какой нибудь InvalidNumber

> это ни разу не NaN.
НЕ Not A Number значит Number. Но document.all НЕ Number. Еще скажите что это все «интуитивно понятно» )

Вспоминается мускульный WHERE NULL = NULL

ЗЫ. Я то это все понимаю, но…

"NaN" — вполне определённый термин. Он имеет своё определение в стандарте.
То, что вы ожидаете от этого термина поведения не из определения, а из своего странного перевода — крайне негативно о вас говорит. Ну примерно, как если бы вы написали следующее:


<i> значит "italic", а значит <i>Добрый день</i> должно перевести фразу на итальянский!

Глупо звучит, правда? Вот что-то такое вы пишете про NaN

Вот именно, тем более это к JS вообще не имеет никакого отношения, а, если уж на то пошло, к стандарту IEEE-754.

а из своего странного перевода

NaN — вообще то не мой странный перевод, а расшифровка аббревиатуры. Еще скажите что это просто "набор букв", а не "Not A Number"


PS. ОЛОЛО
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/NaN


Глобальное свойство NaN является значением, представляющим не-число (Not-A-Number).

Достаточно "авторитетно" ?

А <i> — это и правда italic. Что теперь?
Я сказал, что перевод странный, не расшифровка. Его нельзя переводить "как всё, что не число". Его вообще нельзя переводить. Это термин.

Вы ещё скажите, что italic переводится «итальянский».
Конечно, не поверю. Я вижу слово, которое на русский переводится «италийский».
Вы же не скажете, что «итальянский суп» это «an italic soup»?
Конкретно типографский термин «Italic type» по-русски называется "итальянский курсив", а не «италийский».
Естесственно с взрослением языка никто этот глобальный isNaN не использует, кроме как таких "программистов", как автор.

Ах если бы...

Как-то рано автор начинает палиться: можно было бы еще несколько менее явных wtf-ов накидать вначале, чтоб потянуть интригу(=

В целом бред:


  1. В первом разделе "Система типов и работа с числами" пожаловались на:
    1. Отсутствие целочисленного типа. В реальности внутри движка для подходящих чисел используется целочисленный SMI, сейчас это int31. Для программиста тоже есть, т. к. можно целочисленно сохранить числа до 2 ^ 53 по модулю, т. к. это является подмножеством double.
    2. Отсутствие битовых операций для int16, int8 и т. д. Но они есть для int32, а меньше нужно крайне редко (мне пока ни разу за всю жизнь). Но если надо, всегда можно превратить в более маленькие числа простым отбросом старшей части (и копированием знака при надобности). Оператор "&" отлично справится с этим.
    3. % для double. Не вижу ничего плохого здесь. Используется нечасто, но и % для integer в общем-то не так часто используется, хоть и чаще. Какой смысл писать a - floor(a / b), когда можно просто a % b?
  2. Неверная сортировка по умолчанию. Это смотря с какой стороны посмотреть. Вы не задали компоратор, соответственно, движок не может знать, как Вы хотели посортировать. Хотя я бы для удобства (и скорости) добавил бы метод sortNumbers({order: 'ask|desk'}) либо расширил обычный sort до sort(cmp), где cmp — либо функция, либо строка strings_ask | strings_desk | numbers_ask | numbers_desk.
  3. (1,2,3,4,5,6) === (2,4,6) — ну оператор "запятая" то все должны знать. Самое интересное, что это очень удобно на практике. Я не скажу, что прямо часто использую этот оператор, но иногда он действительно выручает.
  4. Пример с  a = "41"; a += 1;  — за такое надо руки отрывать. Правильно так:
    var a_str = '41',
        a     = +a_str;
    a++;

    Причём в других языках требуется писать примерно также. Что логично.
  5. Можно писать код вида  ~~!![[]]||__``&&$$++<<((""??''))**00==ಠಠ--//\\  — опять же, за такой код надо руки отрывать, если это конечно не обфускация. Можно не значит нужно. Это касается всех языков — во всех языках можно написать код, за который захочется убить.
    • Аналогично и с кодом ('b'+'a'++'a'+'a').toLowerCase() — в нём явно есть опечатка, и это хорошо, что движок выдаёт ошибку.
    • Аналогично и в коде ('b' + 'a' + + 'a' + 'a').toLowerCase() есть опечатка, поэтому неудивительно, что код работает не так, как ожидалось. Правда тут, к сожалению, ошибка не выдаётся — это минус.
  6. input ?? obj?.key ? 'yes' : 'no' — вообще-то это вполне нормальный код в духе "5 + 7 / 2". Если у Вас с ним проблемы, Вы очень плохо знаете язык.
  7. «не отличаются коммутативностью» — при && если первая часть выражения вернула falsy, вторая часть не вычисляется. По-моему, это во всех языках так. С "||" похожая ситуация — если первая часть truthy, вторая часть не вычисляется. При этом возвращается всегда последняя вычисленная часть. Это максимально логично, как по мне, а самое главное — очень удобно на практике. Реально.

    А причина syntax error в последнем выражении в том, что вместо объекта Вы написали блок вида { … code … }. А потом поставили после него && — это явная ошибка. Причём в первом варианте она тоже присутствует, и на самом деле там тоже syntax error. В примере это не указано, т. к., видимо, автор выполнил код с консоли, которая пытается определить объект в начале коде. В обычной жизни в обоих случаях будет syntax error.

    При этом программист js обязан уметь отличать блоки { … code … } и объект {…}. Там, где ожидается выражение — это объект, в остальных случаях блок. Не знают этого только новички.
  8. typeof([1][0]) — снова код, за который надо отрывать руки.
    • for(let i in [1]){console.log(typeof(i))} — аналогично. Программист явно не знает разницу между for in, for of и обычным for. Также программист не прочёл спеки for in и объектов. key обязан быть строкой.
  9. «Значения могут меняться». if (x == 1 && x == 2 && x == 3) // true — потому что использовать оператор "==" нужно только для сравнения с null. Во всех остальных случаях используется ===. Не знают этого только новички. Это логично, что когда программист пишет фигню, получается фигня.
    • document.all == true / false — аналогично.
  10. «На этом он не остановился и прислал мне ещё вот это» — не нужно в качестве this передавать число, т. к. он не предназначен для этого. В данном примере число обернулось в объект, т. к. this предназначен для объектов.

    С другой стороны, лучше было бы бросить в таком случае исключение. Т. е. небольшой минус всё-таки есть. Но на практике я ни разу не встречал, чтобы кто-то передавал числа в качестве this.
  11. «Полное отсутствие в JavaScript стандартной библиотеки» — вообще не понял, что имеется ввиду под этим. Встроенное API у JS очень мощное. А в node.js из коробки идут API, в которых есть вообще всё, что может понадобиться (например, низкоуровневые функции работы с файлами или сетью). Также можно установить или подключить библиотеки (модули) от сторонних разработчиков.
  12. Код:
    for (var i = 1; i <= 2; ++i) {
    setTimeout(function(){
    console.log(i); //3
    }, 0);
    }

    Здесь всё максимально правильно, если Вы не понимаете принцип работы, значит Вы не вообще изучили язык.
  13. «0 4 3 2 1» — опять же, программист обязан знать порядок исполнения. И тут JS просто превосходен как язык. Впрочем, первое время можно обойтись и без знания — в строчках 2–4 сразу видно, что операции откладываются на потом, и достаточно просто иметь это ввиду, а в каком порядке они должны исполняться, можно изучить потом. Таким образом можно будет начать писать сразу.

    В целом это само собой разумеющееся, что раз Вы используете какую-то функцию (в данном случае setTimeout, Promise.resolve…then и т. д.), Вы должны знать как она работает. Особенно в случаях, когда это шикарная функция.
  14. Далее долго рассуждается про асинхронный код. Асинхронный — не значит, что должен выполняться в отдельном потоке. Чтобы выполнить код в отдельном потоке Вам нужно явно создать его и начать выполнять код в нём.
    • Также есть жалоба, что если начать вычислять тяжёлый код, он заблокирует поток. Так такой код нужно либо вынести в отдельный поток, либо вычислять маленькими порциями (причём каждая следующая порция должна запускаться после завершения предыдущей).
    • «выглядит громоздко» — мне кажется, или асинхронный код в целом редко бывает простым? Просто взять и начать писать его без обмена сообщениями/специальных конструкций не получится, т. к. получим проблемы с памятью. JS не исключение, поэтому в нём также приходится либо налаживать обмен сообщениями, либо юзать разделяемую память со всеми её сложностями и специальными синхронизациями.

      Про отсутствие Mutex, select и WaitGroup не скажу, т. к. не пишу такой код; мой код юзает обмен сообщениями, где не нужно думать о синхронизациях.

Но есть и адекватные претензии:


  1. Месяцы нумеруются с 0. Да, бред.
  2. Проблема с регулярками — да, т. к. по факту lastIndex вообще в них не очень нужен был, особенно с появлением итераторов. На крайний случай можно было бы передавать его как дополнительный аргумент (как это сделано в indexOf).
  3. «Он прислал мне этот прекрасный фрагмент JS-кода» — единственная проблема в нём — возможность прибавлять строку к другим типам, после чего будет сделана попытка привести эти другие типы к строке и сложить две строки. Такое автоприведение может привести к тому, что в случае ошибки вместо бросания исключения мы получим скрытую ошибка результата. На практике такое встречается редко, но возможно. Это минус языка.
  4. А также уже перечисленные выше:
    • Отсутствие удобного метода sortNumbers или расширения API sort.
    • Не выдаётся ошибка в ('b' + 'a' + + 'a' + 'a')
    • Не выдаётся ошибка при передаче неверного типа в качестве this.

Итог: JS — на самом деле невероятно красивый и удобный язык, а почти все приведённые минусы либо содержат ошибки, либо приведены просто по незнанию, как правильно, либо приведены из-за недостаточного знания языка.


При этом JS быстр и ест очень мало памяти (на браузер не смотрите, там причина высокого потребления памяти совсем в другом).

PS. При этом один существенный недостаток у JS всё-таки есть: есть много вещей, в которых он не похож на другие языки. Само по себе это не является чем-то плохим (а зачастую, наоборот, хорошим, особенно в случае с JS), но если Вам нужно писать на этом языке очень мало, Вам придётся потратить кучу лишнего времени на изучение языка. При этом если Вы пишите много — это не проблема, т. к. время на изучение будет очень мало по сравнению со временем написания кода. Но тех, кому язык нужен "ненадолго", может ждать облом.


При этом с другой стороны у JS очень низкий порог входа — если Вам действительно нужно написать мало кода, Вы можете сделать это уже прямо сейчас без особого знания языка. Конструкции, описанные в статье, Вам, скорее всего, не встретятся. Всех непонятных конструкций Вы можете просто избегать. При код Ваш будет очень плохим, т. к. Вы не будете знать, как писать его правильно. Но работать код будет. Тем не менее, это не вариант, когда нужно читать или править чужой код. Здесь уже никакого "нулевого порога входа" не будет — придётся изучать весь язык.


PS. При этом есть и другие изъяны, не указанные в статье, например:


  • typeof null возвращает 'object' вместо 'null'.
  • Оператор == не нужен в языке. Даже проверку на null/undefined можно было бы заменить чем-то другим.
  • Отсутствие полностью приватных полей классов без использования замыканий (сейчас уже есть, но только с использованием синтаксиса class).
  • Приведение типов. Уже было упомянуто про "==", но приведение также срабатывает и при < >.

А также:


  • Отсутствие возможности создать полностью типизированную функцию для тяжёлых вычислений (можно поюзать WebAssembly, но это не так удобно). JS несмотря на динамическую типизацию всего-лишь в несколько раз медленнее самых быстрых языков — это не так много, но тем не менее замедление есть.
  • Отсутствие статической типизации — код работает медленнее, чем мог бы (т. к. можно было бы выкинуть многие проверки), а также такая типизация могла бы помогать отлавливать ошибки на стадии компиляции.
  • Данный язык использует сборщик мусора со всеми присущими ему достоинствами и недостатками.

В языке очень много спорных моментов. Например:


  • Я бы смело заменил prototype based OOP на обыкновенный без каких бы то ни было заморочек с this. Ситуаций когда prototype based OOP позволяет сделать что-то что другие системы не могут, и, при всём при этом, это оказалось полезным, мне так и не удалось встретить на практике. Количество же багов в реальном коде, причина которых кроется в плохообработанном контексте, не поддаётся исчислению
  • Неблочная область видимости для var. К счастью теперь у нас есть let & const
  • Архаичные метки
  • Слабая типизация вместо сильной. Я считаю это источник 80+% багов
  • Случайные глобальные переменные. К счастью это во многом в прошлом
  • null & undefined, хватило бы одного
  • слабая стандартная библиотека (я видел ваш комментарий выше, но задумайтесь почему каждая вторая библиотека тащит lodash)
  • switch с waterfall
  • до сих пор нет pattern-matching-а
  • дырявые hash-map массивы. Я думаю инженеры что пишут v8 в полном "восторге" от этой идеи
  • отсутствие каких-либо типов для чисел
  • появление NaN не приводит к ошибке
  • отсутствие многопоточности (уточню — для ноды есть свои костыли, для веба есть воркеры)
  • нет оптимизации хвостовой рекурсии (может вернут?)
  • огромный кусок API это callback-и. Promise-ы придумали относительно "недавно"

Всё выше моё личное ИМХО. На истину не претендую.

prototype based

Это субъективно. Лично я фанат prototype.


Архаичные метки

А Вы их видели в реальных проектах? Если они и используются, только когда другие варианты ещё хуже. Например, когда у Вас числодробилка с 10 вложенными циклами, и Вам нужно выйти из нижних 9. Пишут break ИмяЦикла.


Слабая типизация вместо сильной. Я считаю это источник 80+% багов

Не, по крайней мере в моём коде большинство багов явно не из-за этого. Но я уже итак упомянул об этом недостатке.


null & undefined, хватило бы одного

Они выполняют разную функцию. Но новичкам было бы проще с одним, т. к. они не сразу понимают, в чём отличие.


switch с waterfall

Так это во всех языках так. Но в целом да, я бы сделал бы второй вариант switch, в котором не нужно писать break. В итоге когда тебе нужен waterfall — юзаешь, когда не нужен — не юзаешь. Идеальное решение.


нет pattern-matching

Но ведь есть регулярки, повторяющие этот функционал. Скорость в них не ниже.


дырявые массивы

Полностью согласен :)


отсутствие каких-либо типов для чисел

Зачем? Ну я уже упомянул, что типизированный код бы работал быстрее. Например, если число можно уместить в 2 байта вместо 4, оно может лучше начать влезать в кэш.


Единственные 2 проблемы — снижение скорости после int31, а также недоступность целых чисел после int54. Впрочем, второй недостаток уже решён с помощью BigInt. Причём в будущем его могут пооптимайзить так, что внутри у него могут быть стандартные int64 (=высокая скорость).


появление NaN не приводит к ошибке

Можно занести в недостатки.


отсутствие многопоточности (уточню — для ноды есть свои костыли, для веба есть воркеры)

Вы отстали от жизни. В ноде уже появились полноценные потоки, а не костыли. Также в коде появился WebAssembly (пока experimental).


нет оптимизации хвостовой рекурсии

Она будет. Есть в черновиках ES Next. Но пока недостаток, да.


* var
* случайные глобальные переменные

Нет смысла указывать то, что уже исправили.


——————————


В итоге согласен только с этим (из того, что я не назвал сам):


  1. switch с waterfall (относится ко всем языкам) — стоит сделать второй switch без waterfall
  2. Дырявые массивы
  3. Снижение скорости вычислений после int31, а также после int54 (до int∞).
  4. Появление NaN не приводит к ошибке
  5. Нет оптимизации хвостовой рекурсии (планируется)

Спасибо за обзор. Пара уточнений:


А Вы их видели в реальных проектах?

Нет. Но JS вынужден продолжать их поддерживать и это приводит к неочевидным вещам при парсинге. От чего мы пишем всякие дополнительные скобки


Это субъективно. Лично я фанат prototype

То что в языке методы по факту отвязаны от своих данных в "классах" весьма объективно породило огромное количество багов в реальных продуктах в виду несовершенности программистов\сжатых сроков\whatever. Подчеркну — объективно. С обыкновенной традиционной схемой таких проблем просто тупо нет. А ситуация когда отвязанные this от методов и их данных несут какое-то объективное преимущество… я даже придумать не могу. Согласен это уже субъективно. На мой взгляд на нас просто зачем-то вывалили кишки того, что могло бы быть и внутри. На мой взгляд это явный признак поспешности при проектировке языка.


Они выполняют разную функцию. Но новичкам было бы проще с одним, т. к. они не сразу понимают, в чём отличие.

Не важно что они выполняют в теории. В реальных приложениях, увы, они куда чаще равнозначны и программист понятия не имеет будет ли тут undefined или null. Даже вон люди радеют за использование != null чтобы не писать a !== undefined && a !== null. Т.е. я сейчас сужу о коде который написан за последние десятилетия. Эта фича не выдержала проверку временем. Обычно там хаос с null и undefined. Поэтому, имхо, это 10-е дело что в теории они имеют разные концепции. На практике всё развалилось.


Но ведь есть регулярки, повторяющие этот функционал

Не очень понял причём тут регулярки. Речь идёт об очень удобной языковой конструкции вветвления. У нас ближайший аналог или множество if-else или switch. Ещё очень не хватает do-нотации и pipe-оператора, но это уже совсем мелочи. Жду когда одобрят.


Зачем? (про числа)

Затем чтобы было меньше багов. int-ы это int-ы, а float-ы это float-ы. Не надо мешать яблоки и помидоры. ИМХО, нужно больше порядка.


Вы отстали от жизни. В ноде уже появились полноценные потоки, а не костыли

Можно ссылочку на полноценные потоки и механизмы синхронизации? За nodejs я и правда давно слежу. В стабильные версии node уже завезли? А в браузеры планируют? От worker у меня пока дурные эмоции.

Речь идёт об очень удобной языковой конструкции ветвления

А, если так, то да, очень не хватает.


Можно ссылочку на полноценные потоки и механизмы синхронизации?

Вот. Методы синхронизации:


  • postMessage (сериализация).
  • "передача" массива (ArrayBuffer) в другой поток так, что он перестаёт быть доступен в текущем потоке. Такой метод позволяет избежать сериализации и передачи большого объёма памяти, но при этом не требует от программиста ручных синхронизаций.
  • SharedArrayBuffer.

Имеется возможность обмена данными любого потока с любым.


Появилось это в node v10.5.0 (2018-06-20), а стабильным стало в v12.11.0 (2019-09-25).


А в браузеры планируют? От worker у меня пока дурные эмоции.

Браузерные воркеры также хороши, как и нодовские. Они поддерживают:


  1. Все 3 способа синхронизации, как и в ноде.
  2. Возможность общения любого потока с любым (с помощью SharedWorker).
  3. Возможность не указывать файл. Для этого в качестве URL можно передать следующее:
    URL.createObjectURL(new Blob(['(' + myWorkerCodeFunc.toString() + ')()'], {type: 'application/javascript'}));

Затем чтобы было меньше багов. int-ы это int-ы, а float-ы это float-ы. Не надо мешать яблоки и помидоры. ИМХО, нужно больше порядка.

Я итак уже упомянул про это.

Спасибо за update по потокам. Да, выглядит вкусно.

  • Ну, кстати, this это совсем не про прототипы. This это ничего больше, чем просто контекст функции. Скажем так, специальный "нулевой" аргумент, если можно так выразится.
  • var никогда не брал как что-то плохое. Функциональный скоуп это не всегда плохо. Точнее даже так — не всегда удобно с let/const и блоками, хотя у них и есть свои приимущества тоже. Если уж говорить о минусах var, так это, возможно, хойстинг. И то, это, кмк, вопрос спорный.
  • Метки — мне тоже никогда не мешали, не вижу в них проблемы.
  • Типизация — есть неудобства, есть удобства. Если мы про неудобства — то есть тайпскрипт. Но вообще — большинство ошибок связанных с типами не из-за какой-то кривости этих типов, а из-за того, что программист пишет функцию, которая умеет и стринги, и намберы и массивы в одном аргументе, плюс еще внутри это добро работает с == или какой-нибудь другой бедой.
  • Глобалы есть во многих языках — это, опять же, вопрос адекватности программиста. В JS, тем более, есть свои инкапсулирующие способы — модули, пакеты, ну, или, на крайний случай, функциональное замыкание.
  • null и undefined — тоже, в принципе, логично. Undefined говорит об отсутсвии даже определения (переменной, ключа), когда null говорит о том, что в структуре определение есть, но оно равно пустому значению. Это не одно и то же.
  • Stdlib можно было бы сделать богаче, тут согласен.
  • Не совсем понял про ватерфалл.
  • Про паттерн-матчинг тоже не понял.
  • Вы про дырявость массивов, когда const arr = []; arr[999] = null?
  • С числами можно было бы, возможно, сделать по-элегантнее. С другой стороны, дабла в большинстве случаев вполне хватает.
  • NaN, если я не ошибаюсь и в других языках не ошибка. Могу ошибаться. Но в любом случае, это скорее, как мне кажется, дискуссия о IEEE-754, нежели о языке.
  • Многопоток достаточно просто реализуется что в вебе, что в ноде. В ноде вообще красота — они идут уже из коробки без всяких подпроцессов с, помоему, десятой версии: require('worker_threads').
  • Вроде же поддерживали, нет?
  • Коллбек API завертывается в Promises на раз-два. Конечно, лучше бы, если сразу из коробки, но это, как мне кажется, опять к фундаменту языка не имеет отношения. Скорее это про интерфейс APIшек.

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

Типизация — есть неудобства, есть удобства

Фича не выдержала проверку временем. А в python-е выдержала. Для меня вывод очевиден.


Глобалы есть во многих языках

Речь по "случайные" глобалы, а не про вообще любые. Фича настолько не выдержала проверку временем что её экстренно залатали.


Не совсем понял про ватерфалл

Долго писать. Да и писали об этом много. Кратко — switch-и бывают разные. Те которые по-умолчанию используют waterfall они bug prone. Ну и более многословны.


Вы про дырявость массивов, когда const arr = []; arr[999] = null?

Да. Но я вообще про то, что на уровне движков JS всё что связано с массивами и это груда костылей. Как раз ввиду того как они устроены на уровне языка.


NaN, если я не ошибаюсь и в других языках не ошибка. Могу ошибаться. Но в любом случае, это скорее, как мне кажется, дискуссия о IEEE-754, нежели о языке

Фича не выдержала проверку временем. Поэтому я считаю что эта часть дизайна языка… не очень. То что она не очень не только в JavaScript, ну что ж… Но мы тут JS обсуждаем. Я полагаю ввиду того что он динамически, да ещё и слабо, типизирован, у нас эта "фича" фейлит в разы чаще, чем в других языках. Мы ловим этот NaN слишком часто. Ох уж эти мои любимые формы страховых компаний где все поля внезапно превращаются в NaN. Или интернет магазины. Да всё что угодно. В каком-то смысла NaN это такая визитная анти-карточка JS. Там где для CSS рисуют кружку с поехавшей вёрсткой, JS хейтер может просто написать NaN :D


Вроде же поддерживали, нет?

Ага. Но не долго музыка играла.

Речь по "случайные" глобалы, а не про вообще любые. Фича настолько не выдержала проверку временем что её экстренно залатали.

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


var a = b = 3

Что, делать, конечно же, не стоит.


Да. Но я вообще про то, что на уровне движков JS всё что связано с массивами и это груда костылей. Как раз ввиду того как они устроены на уровне языка.

Видимо я в какой-то параллельной галактике живу. Работа с массивами у меня не вызывает никаких болезненных ощущений.


Фича не выдержала проверку временем. Поэтому я считаю что эта часть дизайна языка… не очень. То что она не очень не только в JavaScript, ну что ж… Но мы тут JS обсуждаем. Я полагаю ввиду того что он динамически, да ещё и слабо, типизирован, у нас эта "фича" фейлит в разы чаще, чем в других языках. Мы ловим этот NaN слишком часто. Ох уж эти мои любимые формы страховых компаний где все поля внезапно превращаются в NaN. Или интернет магазины. Да всё что угодно. В каком-то смысла NaN это такая визитная анти-карточка JS. Там где для CSS рисуют кружку с поехавшей вёрсткой, JS хейтер может просто написать NaN :D

По-моему NaN это как-раз признак дурного тона. Если он где-то вылезает, значит человек не чекает типы, не санитайзит инпуты и вообще надеется в этой жизни на то же, что и автор статьи. У меня вот NaN вылезает в продуктах раз в год при отладке и я не считаю, что это фейл языка. Если все-таки нужны ерроры, так их тоже никто не отменял:


if (Number.isNaN(someResult)) throw new Error('NaN is here, please fix me.')

Ага. Но не долго музыка играла.

Надо будет почитать.

Я не совсем понимаю, что такое "случайные глобалы".

Он имеет ввиду, что если забыть var, то переменная раньше объявлялась в глобальной области, а не в функции. Сейчас это пофиксили — кидается ошибка, но нужно либо юзать ES-модули, либо писать 'use strict' в начале файла.


Работа с массивами у меня не вызывает никаких болезненных ощущений.

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


Разумеется, теоретически можно сделать API без этого требования, но даже в этом случае, если в массиве есть дырки, внутри движок должен заменить их на undefined. Если нет, то можно не заменять.


Причём эти API мешают и программисту — иногда приходится писать .fill(0) либо свою функцию createFilledArray, особенно при функциональном программировании.


Не совсем понял про ватерфалл.

Он имеет ввиду про поведение break в switch. Проблемы две:


  1. Если забудешь break, будет жёсткий баг.
  2. Нужно ставить break после каждого switch, что засоряет код.

Решение — сделать lightSwitch, в котором break не нужен, а программист будет выбирать любой из этих switch по желанию.


Решение 2 — юзать линтеры (анализаторы кода), которые будут оповещать о забытых break, по крайней мере если программист не пометил, что забыл его специально. Так себе решение, первое лучше.

Решение 3 — не использовать switch. Выглядит, правда, костыльно (возможно, потому, что пример вышел слишком вырожденный), но можно делать и вот так:
const greet = userName => ({
    "vova": () => {
        console.log(`Вован заходил в ${Date.now()}`);
        return "Привет, Вован!";
    },
    "jeka": () => `Уже ${new Date().getDate()} число, где деньги?!`
})[userName] || `Здравствуй, ${userName}!`;
Вместо вот такого свитча:
код
const greetSwitch = userName => {
    switch (userName){
        case "vova":
            console.log(`Вован заходил в ${Date.now()}`);
            return "Привет, Вован!";
            break;
        case "jeka":
            return `Уже ${new Date().getDate()} число, где деньги?!`;
            break;
        default:
            return `Здравствуй, ${userName}!`;
    }
}

Данный код медленнее работает, т. к. когда мы обращаемся к полю объекта, указывая не конкретное имя, а передавая его в переменной, движку приходится обращаться к нему не как к быстрому struct (с заранее известными сдвигами), а как к хэшу.


Почитайте про скрытые классы.

Ну в 99.999% случаев вот это "код медленнее работает" никак не отражается на реальной производительности программы. Почти всегда при написании кода на JS не надо думать ни о каких скрытых классах, struct и сдвигах и пр… Достаточно понимать bigO решения и не использовать квадраты там, где есть N logN или N. Это не С++.


Единственное, имхо, код выше выглядит как месиво.

никак не отражается на реальной производительности программы

Что разумеется, неверно. Может один switch и не отразится, но если вся программа будет написана в таком духе, она может работать на порядок медленнее.


Также зачастую switch находятся в цикле, у меня такое бывает относительно часто. И там уже тоже ни в коем случае нельзя сказать, что не влияет.


Почти всегда при написании кода на JS не надо думать ни о каких скрытых классах, struct и сдвигах и пр

Абсолютно неверно.


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


За такой код я бы отрывал руки.


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


Разница между производительностью хэша и struct может быть 10–100 раз. И учитывая, что JS — это язык, в котором обращения к полям объекта чуть ли не в каждой строчке, вся программа тоже замедлится примерно в такое же количество раз.


Я не могу допустить такую растрату ресурсов компьютера.


При этом самое интересное, что писать код правильно здесь не отнимает никаких усилий. Вообще.


понимать bigO решения и не использовать квадраты там, где есть N logN или N.

Разумеется, это самое главное. Но это не значит, что можно замедлить всю программу в 10–100 раз из-за своей тупости, причём когда более быстрый вариант не требует от тебя никаких усилий.


И я бы даже сказал более — более быстрый вариант даже легче писать, т. к. код более понятен, а значит легче писать, читать и поддерживать.


Не использовать скрытые классы и мономорфизм (как в операторах, так и при обращениям к полям объектов) — грубая ошибка, которую могут совершать неопытные программисты на JS.

Мне как-то на просторах хабра попался товарищ который пишет вот так:


            StringBuilder sb = new StringBuilder();
            sb.append("xxx51|").append("<tr data-id='").append(rs.getString("id")).append("'>")
                    .append("<td>").append(rs.getString("number")).append("<a name='x").append(rs.getString("id")).append("'  ></td>")
                    .append("<td  class='td1'></td>")//название реагента
                    .append("<td  class='td2'></td>")
                    .append("<td  class='td3'>").append(rs.getString("date_in")).append("</td>")// дата прихода реагента
                    .append("<td  class='td4'></td>")
                    .append("<td  class='td5'></td>")
                    .append("<td  class='td6'></td>")
                    .append("<td  class='td7'></td>")
                    .append("<td  class='td8'></td>")
                    .append("<td  class='td9'></td>")
                    .append("<td  class='td10'></td>")
                    .append("</tr>\u0003#x").append(rs.getString("id"));

Весь свой код. Вот его аргументация: Ручная работа со String Builder быстрее любых других решений. Все эти ваши шаблонизаторы очень медленные. Из-за таких как вы весь интернет такой медленный. Особенно gmail. И вообще я использую websocket-ы вместо AJAX, так как сериализация и десериализация JSON-а слишком дорога операция. А вы продолжайте писать ваши тормозные gmail-ы.


Полагаю и аргументация и код вам покажутся безумными. Задумайтесь — почему. Потому что человек пытаясь отыграть побольше производительности, пытается это делать совершенно не в тех местах и не тем способом, который даст ему какие-то плоды. Вместо оптимизации действительно узких мест, bottlenecks, он занимается чепухой, которая в конечном счёте, никак не сказывается на производительности его решения. Ибо на фоне других механизмов это капля в море. Но что хуже, он пишет неподдерживаемый код, который в итоге оказывается write only, и, соответственно, дорого обходится владельцу бизнеса.


Вот вся эта история выше ровным счётом из той же оперы. Это хорошо, что вы знаете про hidden classes, это хорошо что вы тоже читали про мономорфизм и компиляцию. Но судя по всему, вы не увидели реальной модели применения всему этому.


Нужно заниматься подобными оптимизациями именно там, где у вас bottle neck. А весь остальной код писать так, чтобы он был как можно более удобный в написании, поддержке и рефакторинге. Именно за это вам платят деньги. Оптимизация на вышеописанном уровне вещей которые не выполняются в больших циклах, через которые не проходят большие объёмы данных, даёт вам столь ничтожные преимущества, что вы никогда не перекроете ими трудозатраты.


Ввиду этого:


За такой код я бы отрывал руки
следовать скрытым классам программист обязан
я не могу допустить такую растрату ресурсов компьютера
Не использовать скрытые классы и мономорфизм (как в операторах, так и при обращениям к полям объектов) — грубая ошибка

Это то самое "мы вам перезвоним" на собеседовании на должность Senior+. Даже при хорошем тестовом задании. Сразу красный флаг. Потому что, увы, на моей памяти, это чаще не непонимание, а самый настоящий фанатизм. Одержимость в какой-то мере. От разработчика на позицию Senior+ требуют гораздо больших навыков, чем умение закодировать рабочее решение. Он должен его понимать с точки зрения долгосрочного планирования. Понимать какие цели стоят перед будущим кодом. По каким критериям его можно оценить. И производительность там только 1 из факторов. Довольно часто — очень минорный.


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


P.S. сорри если я вас чем задел, но мне кажется это важно.

Ручная работа со String Builder быстрее любых других решений. Все эти ваши шаблонизаторы очень медленные. Из-за таких как вы весь интернет такой медленный. Особенно gmail. И вообще я использую websocket-ы вместо AJAX, так как сериализация и десериализация JSON-а слишком дорога операция. А вы продолжайте писать ваши тормозные gmail-ы.

Между мной и ним огромная разница.


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


Я же сделал свой код быстрее, но при этом не испортил его, а по факту сделал его на порядок красивее и приятнее.


Чувствуете разницу?


Разумеется, если бы код сильно портился, я бы не пропагандировал быстроту. Например, я без проблем юзаю в коде for of, функциональное программирование и т. п. — это работает медленнее, но код намного красивее. К тому же со временем такие штуки оптимизатор может оптимайзить до максимальной производительности, т. к. это возможно (что он со временем и начинает делать).


Т. е. я ищу компромисс между скоростью и красотой. Если скорость падает слишком сильно, а красота падает чуть-чуть, я пожертвую красотой. В противном случае я могу пожертвовать скоростью.


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


В любом случае, то, что я сказал изначально, повышает не только производительность, но и красоту либо логичность кода. Использовать хэш как struct абсолютно нелогично. Хэш не предназначен для этого. Вот функциональное программирование предназначено, чтобы его использовали в качестве функционального программирования.


——————————


Кроме того, в Вашем коде есть уязвимость: Вы стали использовать хэш, но почему-то унаследовали его от объекта, соответственно при некоторых именах программа будет иметь совсем другое поведение.

Разумная позиция. Но складывается ощущение, что эти 2 ваших комментария написали 2 разных человека.

Добавлю, что оптимизировать конкретно эту конструкцию, чтобы преобразовать её в последовательность сравнений строк без создания хэш-таблицы — очень просто; и я не удивлюсь, если в современных реализациях JS и Python эта оптимизация уже реализована.

Последовательность сравнений строк — это намного медленнее, т. к. switch может сразу выбрать нужную строку.


Но движок да, может попытаться оптимизировать.


Но даже в этом случае в коде останется уязвимость — он стал использовать хэш, но почему-то унаследовал его от объекта, соответственно при некоторых именах программа будет иметь совсем другое поведение.

Последовательность сравнений строк — это намного медленнее, т. к. switch может сразу выбрать нужную строку.

Магия?


Но даже в этом случае в коде останется уязвимость

? уязвимость? вы о чём?

Магия?

Нет, так во всех языках. switch из-за этого и появился ещё в давние времена, т. к. он быстрее else if и может выбрать нужное значение за O(быстро).


уязвимость? вы о чём?

Вы унаследовали Ваш хэш от объекта. Это значит, что Ваш хэш включает такие свойства, как toString, valueOf, proto, constructor, hasOwnProperty, isPrototypeOf, defineGetter и т. д.


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


Такое неверное использование может привести к уязвимости в программе, т. к. имя, скорее всего, задаётся пользователем.


Чтобы такого не было, если Вы используете объект не как класс/struct, а как хэш, Вы должны наследоваться от нулевого объекта (null).


PS. Если что, я указал в недостатках JS, что в нём засорён Object.prototype.

Если передать в качестве имени одно из этих свойств, вместо ожидаемого результата Вы получите явно что-то другое

userName = {}['toString'];
`Здравствуй, ${userName}!`
// "Здравствуй, function toString() { [native code] }!"

Ой ой. Конец света! Но в целом мысль здравая.


Нет, так во всех языках. switch из-за этого и появился ещё в давние времена, т. к. он быстрее else if и может выбрать нужное значение за O(быстро).

Повторю вопрос: магия?

Я же объяснил, почему он быстрее.


else if работает за O(n) или за O(n * длина строки) (в случае строк). switch работает с совсем другой асимптотикой.

Ты комментарии читаешь в обратном порядке? :) Зря.
Если выразить твою мысль более правильно, то получится:


потенциально компилятор может оптимизировать switch в некоторых случаях так, что это будет быстрее, чем череда IF-ов.

И да, забудь про асимптотику в Switch-е. BigO работает по-другому.

Я думаю, switch практически всегда значительно быстрее else if, даже когда их немного.


Сделать один переход вместо 5 явно лучше. На 100% утверждать конечно не буду.


И да, забудь про асимптотику в Switch-е. BigO работает по-другому.

Внезапно. Так это и работает, у любой штуки есть асимптотика.

Внезапно. Так это и работает, у любой штуки есть асимптотика.

Ты в курсе да, что код O(n!) может быть быстрее, чем O(1)? Или нет?

В курсе, но Вы это преподносите так, как будто асимптотика неважна.

Потому что в маленьких switch-ах (как в примере выше), внезапно, она вообще не важна.

Я думаю, switch практически всегда значительно быстрее else if, даже когда их немного.

Слепая вера — это всегда плохо.

Что мешает проверить?

[tyomitch@phab ~]$ cat sampleFICT.txt sampleACAD.txt sampleMICUSP.txt | tr " " "\n" | time node switch.js
198371 90043 79781 105343 62119 21535 22153 39639 5149 11739
1.13user 0.12system 0:00.83elapsed 150%CPU (0avgtext+0avgdata 35340maxresident)k
0inputs+0outputs (0major+9517minor)pagefaults 0swaps
[tyomitch@phab ~]$ cat sampleFICT.txt sampleACAD.txt sampleMICUSP.txt | tr " " "\n" | time node ifelse.js
198371 90043 79781 105343 62119 21535 22153 39639 5149 11739
1.10user 0.08system 0:00.80elapsed 148%CPU (0avgtext+0avgdata 35188maxresident)k
0inputs+0outputs (0major+9647minor)pagefaults 0swaps
[tyomitch@phab ~]$ cat sampleFICT.txt sampleACAD.txt sampleMICUSP.txt | tr " " "\n" | time node object.js
198371 90043 79781 105343 62119 21535 22153 39639 5149 11739
2.31user 0.17system 0:01.91elapsed 130%CPU (0avgtext+0avgdata 59376maxresident)k
0inputs+0outputs (0major+15985minor)pagefaults 0swaps
[tyomitch@phab ~]$ node --version
v10.16.0


Код
require('readline').createInterface({input: process.stdin})
                   .on('line', online).on('close', onclose);

let the=0, and=0, to=0, of=0, a=0, i=0, was=0, that=0, said=0, have=0;

function online(line) {
        switch (line) {
        case "the":  the++;  break;
        case "and":  and++;  break;
        case "to":   to++;   break;
        case "of":   of++;   break;
        case "a":    a++;    break;
        case "i":    i++;    break;
        case "was":  was++;  break;
        case "that": that++; break;
        case "said": said++; break;
        case "have": have++; break;
        }
}

function onclose() {
    console.log(the, and, to, of, a, i, was, that, said, have);
}


require('readline').createInterface({input: process.stdin})
                   .on('line', online).on('close', onclose);

let the=0, and=0, to=0, of=0, a=0, i=0, was=0, that=0, said=0, have=0;

function online(line) {
        if      (line=="the")  the++;
        else if (line=="and")  and++;
        else if (line=="to")   to++;
        else if (line=="of")   of++;
        else if (line=="a")    a++;
        else if (line=="i")    i++;
        else if (line=="was")  was++;
        else if (line=="that") that++;
        else if (line=="said") said++;
        else if (line=="have") have++;
}

function onclose() {
    console.log(the, and, to, of, a, i, was, that, said, have);
}


require('readline').createInterface({input: process.stdin})
                   .on('line', online).on('close', onclose);

let the=0, and=0, to=0, of=0, a=0, i=0, was=0, that=0, said=0, have=0;

function online(line) {
        ({the:  ()=>the++,
          and:  ()=>and++,
          to:   ()=>to++,
          of:   ()=>of++,
          a:    ()=>a++,
          i:    ()=>i++,
          was:  ()=>was++,
          that: ()=>that++,
          said: ()=>said++,
          have: ()=>have++
         }[line] || (()=>0))();
}

function onclose() {
    console.log(the, and, to, of, a, i, was, that, said, have);
}



Входные файлы взяты с www.thegrammarlab.com/?nor-portfolio=1000000-word-sample-corpora
так во всех языках. switch из-за этого и появился ещё в давние времена, т. к. он быстрее else if и может выбрать нужное значение за O(быстро).

Особой разницы нет, количество сравнений будет одинаковым: C++, C#
Сделайте 5 case'ов+default и для switch будет переход по таблице, а для elseif так и останутся последовательные сравнения. А для 3-4 case'ов будет метод половинного деления (если не забыть хотя бы -O1).

Я в асме не очень разбираюся, но вроде всё-равно однаковые остались:
Плюсы, Шарпы

В поле «Compiler options» надо вписать "-O"
Это если с числовым значением.
А если со строковым? Применительно к JS речь ведь шла именно о строках.

Почему бы вам самому не сделать пример и не выложить ссылку?

Вы уверены, что функция пооптимайзилась? Там есть функция, которая возвращает, пооптимайзилась ли она (и другая функция для оптимайза).


Если какой-то код исполняется мало раз, его невыгодно оптимайзить, т. к. это будет работать ещё медленнее.


И желательно привести разные примеры. С разным количеством case, разными длинами строк (а не только из одного символа) и т. д. Если оно вообще никогда не оптимизируется, это одно. Если оптимизируется при определённых условиях, возможно, JIT делает это специально.


PS. В конце заметил, что это не Javascript :)
Речь шла в первую очередь о JS. Его оптимизатор очень продвинутый.

Приведите хоть один пример кода на JS, который бы подтверждал существование в V8 или в любых других реализациях JS оптимизаций для switch со строками.

Я не силён в ассемблере. Где-то читал, что такая штука есть, но, возможно, это была Java. Но учитывая, сколько бабла влито в JS, думаю, этот момент сделан так, чтобы работало как можно быстрее.

Ассемблер и не нужен: приведите пример кода, где бы замена if..else на switch привела к ускорению. Командой time вы ведь пользоваться умеете?

Я вам выше запостил мой собственный пример, где никакого ускорения от такой замены не увидел.

Но переход от «Последовательность сравнений строк — это намного медленнее, т. к. switch может сразу выбрать нужную строку.» к «Где-то читал, что такая штука есть, но, возможно, это была Java.» (а может, просто приснилось) — уже прогресс.
Со строками могу ответить только за C++. Там в case не могут фигурировать строки и выбор за программистом, или использовать кучу if-else сравнений, или сравнивать хэш строки и compile-time хэши значений (https://stackoverflow.com/questions/650162/why-the-switch-statement-cannot-be-applied-on-strings#46711735).
Каким образом switch может сразу выбрать нужную строку, не сравнивая userName поочерёдно со строками «vova» и «jeka»?
  1. Считаем хэш от строки (хэши от case уже предпосчитаны). Таким образом, преобразовываем строки в числа. Размер хэша должен быть сопоставим с количеством case.
  2. Составляем таблицу переходов для каждого из хэшей. Там, где перехода для этого хэша нет, пишем 0 (для минимум половины хэшей перехода не будет).
  3. Делаем переход сразу в нужное место за O(1).

Суммарная сложность — O(длина строки).


При коллизиях либо меняем алгоритм хэширования (это делается на стадии компиляции), либо проходим циклом или else if по списку case, которые имеют один и тот же наш хэш. При этом цикл будет применяться не на все case, а лишь на небольшую их часть при попадании в коллизию.


Иногда можно ускорить алгоритм, учитывая длину строки. Если длина у каких-то case не совпадает с нужной, их можно сразу отбросить. Проверять каждый из них с else if не придётся. Сложность останется O(длина строки).


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


——————


В целом обычные хэши работают похоже (хотя отличия есть), но они как объекты должны поддерживать намного больше операций, и компилятор не может прооптимизировать их также, как switch.


Например, в switch список case задан уже на стадии компиляции, и компилятор может подобрать оптимальный алгоритм хэширования. Список не может расширяться в рантайме. Из списка не могут удаляться элементы. Списку нет нужны поддерживать кучу разных операций — в switch компилятор сделать только именно то, что нужно, используя минимум кода. Количество switch в коде ограничено, а значит меньше требования по памяти. В switch не требуется создавать кучу функций, а значит код красивее и может потреблять меньше памяти (т. к. goto, думаю, оптимайзнее создания вызова функции). И т. д.


PS. Могу чуть ошибаться в принципах работы switch, т. к. не проверял себя перед написанием этого комментария. Но в целом switch — очень быстрая конструкция, особенно на числах.


Это что-то типа как задать массив для goto, а потом сразу сделать прыжок в нужное место по индексу в массиве. Или как задать массив функций, и вызвать нужную по индексу (но функции имеют больший оверхэд, а также значительно меньшую красоту).


——————


При этом я допускаю использование хэшей в коде, например, в таких случаях:


const countByName = {
    __proto__: null,
    vanya: 1,
    jenya: 5,
    anton: 10,
};

//…

const count = countByName[name];

В данном случае код выглядит максимально красиво и логично. В начальном же примере с функциями напрашивался switch.


Хотя то, что я упомянул про скрытые классы было чуть не к месту, т. к. switch тоже поюзает хэши, хоть и более быстрые.

Тадам! Мы начали с хешей и вернулись к хешам.


Но скорее всего (я не проверял) JIT скомпилирует типовой switch в набор последовательных IF-ов, а не в хеш, потому что так будет тупо быстрее. Да, даже несмотря на на то что это O(n), вместо O(1).

Тадам! Мы начали с хешей и вернулись к хешам.

Я обновил комментарий, перечитайте, да, признаю, что про скрытые классы упомянул не к месту.


Но скорее всего (я не проверял) JIT скомпилирует типовой switch в набор последовательных IF-ов, а не в хеш, потому что так будет тупо быстрее. Да, даже несмотря на на то что это O(n), вместо O(1).

Без разницы, JIT преобразует в то, что будет работать быстрее всего. С объектом у него такой возможности нет (хотя на самом деле он может попытаться понять, что эта штука похожа на switch, и обработать её похоже).


+Я говорил, что в некоторых случаях можно даже не считать хэш, а можно сделать переход по длине строки, а там уже сделать тупые проверки. Это, возможно, быстрее, чем всё else if.


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


В любом случае, JIT выберет лучший вариант, и switch даёт ему полную информацию для этого.

В любом случае, JIT выберет лучший вариант, и switch даёт ему полную информацию для этого.

Второй вариант даёт ему ровно столько же информации.

Это верно, но сможет ли он понять это, зависит от его продвинусти.


В любом случае, код со switch красивее, и даже если бы вариант с функциями был бы быстрее, я бы, вероятно, предпочёл бы тут switch.

В любом случае, код со switch красивее

Существенная часть сообщества не использует switch именно в виду его уродливости и избыточности кода (break-и и лишние {}).


Красота понятие субъективное.

Как же много народу любят высказывать предположения о производительности (или даже не предположения а уверенность) не представляя реального положения вещей. Я предлагаю не гадать и вживую посмотреть в какой ассемблерный код компилируется switch движком v8 и соотвественно увидеть есть ли там хеши, jump-ы по таблице или это просто набор if-ов. Тем более это совсем не сложно. Запускаете в ноде с флагом node --print-opt-code --code-comments script.js и получите в консоли ассемблер в который компилируется js (только для этого функцию со switch-ем надо разогреть циклом либо обернуть глобальной функцией %OptimizeFunctionOnNextCall(fn) и запустить с флагом --allow-natives-syntax)

Мысль, конечно, замечательная. Но есть одна засада. Если человек не понимает asm (например я), то что мне с этим asm кодом делать? :)

В Python, где синтаксис switch отсутствует — именно так и принято писать.

Тогда Python хрень по сравнению с JS. Потому что такой код слишком медленный, что недопустимо, т. к. код обязан быть быстрым (код на JS — очень быстрый), а во-вторых, потому что это неудобно и семантически неверно.

Никто не пишет числодробилок ни на Python, ни на JS, так что замедление работы switch даже вдесятеро не имело бы никакого значения.

Я писал на эту тему подробный пост: habr.com/ru/post/166341

Если Python такой медленный, это сильно ограничивает область его применения.


Да и вообще, скорость языка — это его характеристика. Если даже ты не пишешь тяжёлых вычислений, всегда приятнее, когда код работает быстрее.


+Бывает всякое: сегодня не пишешь, а завтра тебе понадобилось добавить более менее тяжёлую функцию.


Я считаю, что это круто, когда язык одновременно быстрый, и вместе с этим красивый и максимально удобный. И JS — как раз один из таких языков.


——————


Взять тот же PHP. Язык медленный, но кто-то скажет, ну и пусть, страничку можно и так сгенерить. А вот у меня не то, чтобы супер-сложная страничка, генерится 1 секунду :( И всё лагает. Тот же код на JS отработал бы за 50 мс. Сколько ни оптимайзил, всё-равно было медленно. Обидно. Уменьшить алгоритмическую сложность было нельзя (она итак была низкая). Разве что переносить на другой более мощный сервер.

Я считаю, что это круто, когда язык одновременно быстрый, и вместе с этим красивый и максимально удобный. И JS — как раз один из таких языков

Не говорите это системщикам. Побьют. Больно побьют. То что JavaScript один из самых быстрых языков, среди динамически типизированных, не делает его быстрым.


И не верьте бенчмаркам которые говорят обратное. Если вы достаточно углубитесь в то, что они тестируют, то увидите одно из:


  • там тестируют c++ код против c++ кода (js скорее декорация)
  • там тестируют совершенно не идиоматический js код
  • туда воткнули какой-нибудь asmjs
  • там тестируют разные имплементации
  • прочие уловки которые почти никогда не встречаются в реальной жизни

Я для себя принял к сведению для простоты оценки, что JS медленнее плюсов в среднем от 4 до 100 раз. В исключительных случаях от 2 до 1000.

До 100 раз

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


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


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


Конечно, я не буду спорить, что в API можно найти какую-то функцию, которая либо по дизайну такая медленная, либо просто ещё не оптимизирована. Но на практике такое либо не встречается, либо на крайний случай просто не используется.


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

Либо правда, но тогда так можно сказать про любой язык.

Про любой динамически типизированный. Python, PHP, Ruby, JavaScript, Groovy, etc.


Также я не буду спорить, что если код писать неправильно на JS, он может быть очень медленным

Не-не. Тут всё наоборот. Если писать на JS правильно (идиоматично), то он очень медленный. А если писать неправильно, то может быть довольно быстрым.


Если ты постоянно ловишь себя на том, что хочешь писать на JS неправильно, делая акцент на скорости, то тут одно из двух:


  • ты используешь не тот инструмент
  • ты пишешь код неправильно
Про любой динамически типизированный. Python, PHP, Ruby, JavaScript, Groovy, etc.

Нет, вообще про любой язык. Динамическая типизация тут не причём, т. к. в любой языке можно ввести API, которое будет медленным, и если кто-то заюзает это API вместо того, чтобы написать без него, его код тоже станет медленным.


+Вы поставили JS рядом с PHP, только вот PHP, насколько я знаю, где-то в 20 раз медленнее JS. Это без учёта отсутствия асинхронной модели, которая может увеличить разрыв в производительности ещё даже не один, а вплоть до нескольких порядков.


Если писать на JS правильно (идиоматично), то он очень медленный. А если писать неправильно, то может быть довольно быстрым.

Нет, всё наоборот, правильный код на JS будет очень быстрым. В т. ч. частично из-за особенностей языка.

API тут не причём. Речь в динамической природе языка. Типовой идиоматический JS код это когда у тебя практически всё это либо hashmap-ы, либо триллион вариаций hidden классов.


+Вы поставили JS рядом с PHP, только вот PHP, насколько я знаю, где-то в 20 раз медленнее JS.

О_о. Туда конечно вбухали меньше бабла, и производительность PHP7 может быть несколько ниже, но это язык с одной полки.


Нет, всё наоборот, правильный код на JS будет очень быстрым. В т. ч. частично из-за особенностей языка.

Ты кажется вообще не понял, что я имею ввиду. Правильный код это когда ты не держишь в своей голове всякую чепуху про hidden class, деоптимизацию, "не использовать delete", юзать switch против hashmap, и многое многое другое.


Ты просто пишешь бизнес-логику. Всё. Как только ты начинаешь сильно много думать об технических нюансах влияющих на производительность — ты начинаешь заниматься ерундой. Просто берёшь — выкидываешь JS, и берёшь производительный язык сразу. И пишешь действительно быстрый код.


Твоя мысль примерно такова: на javascript следуя ряду правил и подходов можно написать код который будет не супермедленным. Правильная мысль — на javascript так писать не надо, надо сразу писать на правильном языке и тогда будет даже быстро.

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

Так я и не спорю, что я люблю JS в том числе за то, что туда вбухали кучу бабла =) Если бы это было не так, я бы любил его меньше, т. к. он был бы менее производительным.


При этом PHP я бы всё-равно ненавидел, т. к. даже если превратить по скорости в C++, язык всё-равно ужасный.


Правильный код это когда ты не держишь в своей голове всякую чепуху про hidden class, деоптимизацию, "не использовать delete", юзать switch против hashmap, и многое многое другое.

Нет, это не будет правильным кодом. Вы меня не переубедите.


При этом правильный код в моём понимании полностью соответствует идеологии языка. Если что-то является классом, мы должны юзать его как класс. Это максимально логично и красиво.


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


——————


Тем не менее, если Вы хотите от меня признание: не было бы в JS hidden classes, я бы не любил его, т. к. посчитал бы это настолько серьёзным недостатком, что моя любовь бы сильно уменьшилась либо пропала бы вообще.


Даже один действительно серьёзный недостаток может испортить язык.


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


Ты просто пишешь бизнес-логику. Всё. Как только ты начинаешь сильно много думать об технических нюансах влияющих на производительность — ты начинаешь заниматься ерундой. Просто берёшь — выкидываешь JS, и берёшь производительный язык сразу. И пишешь действительно быстрый код. Твоя мысль примерно такова: на javascript следуя ряду правил и подходов можно написать код который будет не супермедленным. Правильная мысль — на javascript так писать не надо, надо сразу писать на правильном языке и тогда будет даже быстро.

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


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


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

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


Сейчас же у вас есть углубленное понимание некоторых моментов языка, и почти полное непонимание его бизнес-модели. Чем-то это вызвано я не знаю. Но думаю время расставит всё на свои места.


Думаю этот бессмысленный продолжать смысла нет.

Теперь мы судим язык по бизнес-модели)


А при написании быстрого кода достаточно следовать мономорфизму.

Если Python такой медленный, это сильно ограничивает область его применения.

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

Я написал почему, перечитайте мой комментарий.


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

Код, кстати, неправильный, не хватает вызова
Надо так
const greet = userName => ({
    "vova": () => {
        console.log(`Вован заходил в ${Date.now()}`);
        return "Привет, Вован!";
    },
    "jeka": () => `Уже ${new Date().getDate()} число, где деньги?!`
  })[userName]
  || (()=>`Здравствуй, ${userName}!`)
)()

Или, если делать максимально похоже на типичный свитч-кейс
то так
const greet = userName => {
    let greeting = "";
    let logString = "";
    ({
        "vova": () => {
            logString = `Вован заходил в ${Date.now()}`;
            greeting = "Привет, Вован!";
        },
        "jeka": () => {
            logString = 'Он всё ещё сюда ходит.';
            greeting = `Уже ${new Date().getDate()} число, где деньги?!`;
        },
    }[userName]
        || (() => {
            // Default
            logString = `Заходил ${userName}`;
            greeting = `Здравствуй, ${userName}!`;
        }))();
    console.log(logString);
    return greeting;
}

Честно говоря, вариант со свитчем мне нравится куда больше. Я сразу вижну какие варианты, сразу вижу дефолтный кейс, и парсить это ментально куда проще.


Ну и break после return писать не надо, так что ещё 2 строчки можно выкинуть из свитча.

Простота ментального парсинга — исключительно вопрос привычки.
const greet = userName => ({
    "vova": () => {
        console.log(`Вован заходил в ${Date.now()}`);
        return "Привет, Вован!";
    },
    "jeka": () => `Уже ${new Date().getDate()} число, где деньги?!`
})[userName] || `Здравствуй, ${userName}!`;

function greetSwitch(userName) {
  switch (userName) {
    case "vova":
      console.log(`Вован заходил в ${Date.now()}`);
      return "Привет, Вован!";
    case "jeka":
      return `Уже ${new Date().getDate()} число, где деньги?!`;
    default:
      return `Здравствуй, ${userName}!`;
    }
}

Вот второй вариант может и выглядит "по-мезозойски", но КМК куда читабельнее, а ещё куда более "гит-френдли", там добавление кода в вариант jeka например не даст большого диффа. Это если попытаться объективно измерить "читемость". Вопрос привычки — да, но вопрос чистемости имеет под собой вполне измеряемые характеристики (хотя я их и не смогу сейчас формализовать). Код на перле или на J тоже можно назвать чистемым — вопрос только в привычке.

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

Получается то же самое, но чуть вербознее, разве нет? Если есть специализированный синтаксис для этого, почему бы им не воспользоваться? Он ведь лучше передает намерения, чем "посмотри на кучку замыканий и immeditely called объектов, и догадайся, что тут имелся ввиду свитч сдефолтным кейсом если ни один из вариантов не подошел". Можно догадаться, не спорю. Но ведь это труднее чем когда то же самое написано прямым текстом.

но чуть вербознее, разве нет?
Если по символам считать, то свитч-кейс вербознее будет, особенно, если дефолт не нужен.
И есть ещё пара специфичных плюсов сомнительной ценности у словарей вместо свитча
Например
  • гибкость в виде возможности динамической модификации — добавления/удаления кейсов, замены логики в них
  • для некоторых случаев весь этот «свитч-словарь» можно вынести и переиспользовать
Он ведь лучше передает намерения, чем
А можете, пожалуйста, явно написать намерения, которые вы подразумеваете? Просто для меня это ≈«выполнить код, соответствующий значению выражения, в соответствии с заранее известным сопоставлением значение->код», и это прямо транслируется в
«в соответствии с заранее известным сопоставлением» => поиск по словарю,
«значение->код» => словарь с функциями в качестве значений
«выполнить код» => исполнение функции.
И есть ещё пара специфичных плюсов сомнительной ценности у словарей вместо свитча

Действительно сомнительной. Если бы кто-то в таком словаре действий еще динамически их бы менял, то представляю какая веселая отладка была бы в случае чего.


А можете, пожалуйста, явно написать намерения, которые вы подразумеваете? Просто для меня это ≈«выполнить код, соответствующий значению выражения, в соответствии с заранее известным сопоставлением значение->код», и это прямо транслируется в

В первом случае я сразу вижу "есть значение, и у него есть N возможных значений, на каждое вот такое действие".


Во-втором создается объект с полями, потом идет обращение к этому объекту по неизвестному полю, потом идет обработка возможного falsy-значения. Можно ли это распарсить как свитч? Можно. Но — это сложнее, чем когда это прямо написано.

Можно ли это распарсить как свитч? Можно. Но — это сложнее, чем когда это прямо написано.
Ну, да, если в уме переводить на русский распознавать свитчи, то да, свитч проще узнать в себе самом, а для меня сам синтаксис свитча, не встречающийся в языке вообще нигде больше (кроме меток, которые никто не использует) менее нагляден и узнаваем, чем «найти значение в словаре». В смысле, это же реально синтаксис GOTO на метку (case), только более многословный и с возможностью сделать в одно слово GOTO BREAK.
Нужно ставить break после каждого switch, что засоряет код.

Если это становится проблемой, то я обычно рефакторю так, чтобы получить код вида


mapCodeToError = (code: number): Error? => {
  switch(code) {
    case 400: return new Error("BadRequest");
    case 401: return new Error("Unauthorized");
    ...
    default: return;
  }
}

или:

dispatch = (action) => {
  switch(action.name) {
    case "add": return this.add(action.item);
    case "del": return this.remove(action.itemId);
    ...
    default: throw new Error("Action.Unknown")
  }
}

Срабатывает практически всегда, забыл уже, когда последний раз понадобился break.

Тоже делаю так, когда можно и когда это красиво, но не всегда оба этих условия соблюдены. А использовать IIFE для этого некрасиво (да и увеличивает вложенность кода).

Я не совсем понимаю, что такое "случайные глобалы"

Это то от чего нас спасли:


  • use strict
  • import/export modules
  • nodejs commonjs

Но ещё осталась эта проблема в:


  • no-module code
  • no-module script
  • в старом говнокоде (например чужие подключаемые либы\виджеты)

Например:


<script>var oops = 1; // window.oops === 1</script>

Видимо я в какой-то параллельной галактике живу

И ты и я. В неправильной вселенной живут разработчики V8, webkit, Firefox. Им приходится очень много потеть чтобы заставить наш код работать быстро, в то время как наши массивы чуть ли не по спеке ведут себя словно они не массивы вовсе, а некие hash-map-ы. Держать их реальными hash-map-ми движок себе позволить не может, hash-map очень дорогая и медленная структура (несмотря на добротную асимптотику).


Если он где-то вылезает, значит человек не чекает типы, не санитайзит инпуты и вообще надеется в этой жизни на то же, что и автор статьи

Всё так. Welcome to real life! А теперь представь если бы у всех эти программистов оно падало ещё на этапе разработке где они миксуют строки\числа\бог-знает-что. Тогда зайдя на форму очередной страховой компании ты мог бы заказать полис, а в интернет магазине купить товар. А не наслаждаться NaN :) (но ещё останется легендарное [object Object]).

в то время как наши массивы чуть ли не по спеке ведут себя словно они не массивы вовсе, а некие hash-map-ы

Не, то что массив похож на hash-map никак не влияет на оптимизацию. Сделано так: до 32 млн дырок (128 МБ SMI/pointer) массив создаётся как настоящий массив, а дальше уже как hash-map.


В итоге в реальной жизни массивы — это в 99.9999% случаев реальные массивы. Но это не значит, что дырки им не мешают. Почему всё-таки мешают, я написал в этом комментарии.

Приятно поболтать со знающими программистами, vitaliy2 & faiwer. Если хотите, добавляйтесь в телегу: @mwhyte.

Фича не выдержала проверку временем. А в python-е выдержала. Для меня вывод очевиден.
Нигде не выдержала, в Python тоже.

А в го нет дженериков)

Честно стыренный комментарий с пикабу:

В общем то, все логично. Не баг а фича. Это называется неявное приведение типов.

JavaScript caм в не понимает, с какого перепугу ты пытаешься из строчки вычесть число. Но ладно, хозяин барин, попробую привести строку к числу. Надо же, получилось, JS справился.

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

Что, ты поставил минус перед строчкой? Должно быть, ты ненормальный пытаешься сделать его отрицательным. Ну ок, превращу тебе двойку в число и сделаю его отрицательным. Вуаля.

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

Я планирую перейти. Планирую с Python. Сугубо бизнес, ничего личного
Чтобы тут не писали хейтеры JS и токсичные выпускающие ядовитую слюну Seniors, JS Сейчас среди ТОП языков на рынке предложений, JS топ язык для Web. И если кому-то он не нравится, так нефиг даже совать сюда нос, мы используем языки, как инструменты для решения задач, это бл№ть не РЕЛИГИЯ, А вы рили как дитятки в песочнице "- Моя машинка круче! А в твоей не такая типизация и вообще не похожа на нормальную машинку"
Половину что приводится из примеров, которые мы максимально замудряете, не будет использоваться, вы такую фигню пишите, кому эта херня нужна? И что это, если не понты написания кода не решающие ни одну задачу! "- Под капот языка залез, ой я какой Умный!". Ты чё бл% contributor ЭкмаСкрипта? Всё просто, взял инструмент -> решил задачу -> забыл, Уаля! А не сидишь и кодишь всякую фигню, мол смотри этот язык возращает False где логичнее было бы True.
Приходя в чужой монастырь, засуньте свои правила далеко в одно место!
та да, наверное даже если вдруг когда нибудь в далёком будущем, сам js канет в небытиё, тема wtf js будет ещё долго радовать народ лулзами )
НЛО прилетело и опубликовало эту надпись здесь
Хочется заметить, что в самой статье никто не говорит, что JS язык не серьезный или не развитый.
Хочется пожелать аффтору и всем злопыхателям… не срать там, где уже было насрано до вас.

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

То, что автор путает сам язык и платформенный API, только подчеркивает, как он проникся идеей изоморфного подхода. Ну, с кем не бывает.
Скрытый текст
<!--закрывает тег в начале статьи-->
</sarcasm>