Pull to refresh

Comments 149

Метки можно назначать циклам for или блокам кода в JS… А вы не знали? Я — так точно этого не знал. Затем на эти метки можно ссылаться и использовать команды break или continue в циклах for, а также применять команду break в блоках кода.

Прям эволюция сишного goto.

JS напоминает в некоторых элементах Perl

Это не фича только лишь JS. Она доступна и в др. языках, к примеру Java. Вполне логичный подход, когда надо выйти из глубоко вложенного цикла
Правда иногда можно случайно вывалиться из switch по незнанию
Программист это профессия предполагающая применение мозга. Если что ;)
Забавно применять мозг с каким-нибудь undefined behavior
Что именно ub в том, когда видишь «continue label»?

Я не говорю, что это нужно постоянно такое использовать. Я говорю о тех редких случаях, когда по-другому будет на уровне «почесть левое ухо ногой правой пятки».
Суть в том что есть вещи которые приходитс изучать эмпирически, а не логически. undefined behavior — как раз одна из таких вещей и сколько мозг не прикладывай — пока не глянешь в сорцы или не скомпилируешь — не угадаешь что ожидать на выходе. Повезло, если в компиляторе/интерпретаторе не слишком много ошибок. Поэтому многие неявные механизмы никак не связаны с применением мозга.
Continue label посреди кода конечно же не является ub, однако без диагностических ошибок можно попасть не совсем туда, куда ожидалось. Прыжок из/в области видимости switch может быть настолько же неожиданными как и 0x873162387 вместо 0х0 в неинициализированном поле структуры — не все знакомы с текстом стандартов языка и помнят все нюансы из него.

Для этого есть документация.
И в JS, афаик, нет таких мест, как, например в php документации, как сейчас помню: "если вы передадите параметром тип номер результат функции непредсказуем".
Есть нелогичные моменты, но они как раз покрыты документацией.

Речь изначально не ограничивалась JS.
В Go тоже есть такая же конструкция. Обычно используется для выхода из бесконечного цикла в блоке select
LOOP:
    for {
        select {
	case message:= <-messageChannel:
		fmt.Println(message)
	case canselMessage:= <-canselChannel:
		fmt.Println(canselMessage)
		break LOOP
	}
    }
А встроенной конструкции loop не завезли как в Rust?

про "конвеер" стоит упомянуть что он исполняется справа налево, из вашего примера это не очевидно

хм… это существенно снижает полезность конструкции

если в a |> b |> c вначале исполняется c — то это совсем неинтуитивно, но внизу уже вроде объяснили, что все-таки "слева направо"

да, беру свои слова обратно, исполняется слева на право, т.е. как бы первое значение в строчке проходит все функции и возвращается то что "вылезло" с другой стороны.

Что значит «справа налево»? a |> b |> c вызовет сначала b, потом c. Так и в спецификации написано, и в других языках (F#, Elm) эта конструкция так же работает.
let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"

let result = "hello"
  |> doubleSay
  |> capitalize
  |> exclaim;

result //=> "Hello, hello!"

Отсюда. Обычный код (1-ая строка) исполняется справа-налево (doubleSay расположена правее capitalize и тем более exclaim), а piped-код как раз слева-направо.

Можно пояснить?
«справа налево» это не как в баше/эликсире?
Именно как в эликсире.
UFO just landed and posted this here

Это смотря что вы понимаете под справедливостью.

Метки нужны для выхода во внешний цикл из глубоко вложенного. Вполне легитимное дело.
Это дело явно требует пересмотра архитектуры.
Это дело требует выделения отдельной функции под циклы, это сложно назвать «пересмотром архитектуры».

Но иногда программисту приходится не сочинять алгоритм, а кодить готовый. В такой ситуации заведение лишней функции там где в оригинале ее не было не повысит читаемость кода, и понизит. Вот тут-то все эти метки порой и пригождаются.
UFO just landed and posted this here
Почему Вы думаете, что эта функция написана оптимально?
А, вообще, это, в первую очередь, пример запутанного кода, что подтверждает аргументы против неструктурного контроля выполнения.
Качество интерпретатора соответствует качеству языка, не так ли?
UFO just landed and posted this here
Где же ответ? В функции есть лишние присваивания, не оптимальное использование switch, но Вы делаете акцент на экономии процессорного времени. Где же оно в этом плохо написанном коде?
UFO just landed and posted this here
с учетом RFC 4180. Это не займет много времени.
Увы, коллега, не похоже на то. Я начал смотреть, что такое php_mblen и уже это заняло много времени с учётом контекста функции. Дело в том, что php_mblen в зависимости от значений макросов при сборке и переменных окружения может быть гарантировано однобайтным. Тогда неясно, на каком основании выполняется эта часть
*ptr == '\0' ? 1
потому что в таком случае цикл может остановиться только по достижении конца массива и вообще нет смысла его перебирать, так как сразу можно перейти к концу(экономия процессорного времени, да).
Указанная Вами функция не является самосотоятельной частью, воплощающей стандарт, а зависит от объемлющего кода. Откуда мне знать обо всех задумках или ошибках, которые были здесь допущены. Например, почему в многобайтной кодировке, точный тип который задаётся окружением, символ перевода строки задаётся одним char? Здаётся мне, что в этой функции проблемы не исчерпываются использованием неструктурных переходов.

Ну и, наконец, неужели для того, чтобы понять, что двойное копирование строки для 1-байтных последовательностей — это нечто неоптимальное, Вам нужен другой алгоритм?
UFO just landed and posted this here
В мультибайтовых кодировках существует обратная совместимость с ASCII
Из чего это следует? Я в стандарте этого не видел. Можете указать, где это сказано точно? Если это так, то алгоритм можно написать намного эффективней, так как mblen тяжеловесна.
И всё становится предельно ясно.
Тогда объясните мне, пожалуйста, как это понимать: *ptr == '\0' ? 1 для таких настроек:
#ifndef HAVE_MBLEN
# define php_mblen(ptr, len) 1

И почему при обратной совместимости в качестве конца строки используется не '\0', а null wide character.
Мы всё еще рассматриваем пользу ссылок в некоторых случаях.
Можно конкретней? Ссылок на то, что
last_chars[0] = last_chars[1];
last_chars[1] = *ptr;

Приводит к двойному копированию последовательностей 1-байтовых символов, или что-то ещё?
UFO just landed and posted this here
Согласно RFC3629, например, у кодировки переменная длина символа. Можно ли в таком случае обойтись без mblen?

RFC3629 — это же UTF-8, или я что-то не понимаю? Там как раз все просто: никаких отличий от однобайтовых кодировок конкретно в этой задаче (символы CR, LF и конец строки кодируются одинаково и с частями других символов их спутать невозможно).


Собственно, UTF-8 специально же так проектировалась чтобы большинство старых строковых алгоритмов могло с ней нормально работать.

Согласно RFC3629
При чём тут UTF-8? В стандарте ISO C на mblen, который используется в определении php_mblen, сказано, что он определяет количество байт символа кодировки, определяемой по LC_CTYPE. На каком основании в алгоритме делаются вещи, будто бы LC_CTYPE задаёт именно UTF-8? Повторю ещё раз, покажите, пожалуйста, место, где Вы увидели в стандарте на mblen, что
В мультибайтовых кодировках существует обратная совместимость с ASCII

Можно ли в таком случае обойтись без mblen?
Если же задачей всё ограничивается именно UTF-8, то, конечно, mblen — это избыточный тормоз.
Палка на двух концах.
Это объяснение? Потому что совместимо, поэтому не совместимо?
Уверены? :)
Удивляюсь, что это нужно объяснять. :)
Как в таком случае можно рассуждать об оптимальности кода?
Потому что для оценки не всегда нужен весь контекст, а вот для создания корректного алгоритма — нужен. Особенно, если окажется, что изначальный алгоритм некорректен и сравнение бессмысленно.
В этом-то и проблема плохого кода, что его потом и не перепишешь, не нарвавшись на остальные пахучие особенности общего кода.
Какой-то индусский наркоман писал эту функцию.

По-моему 1-ый switch будет проще и быстрее так
		if (inc_len == 0) {
			break;
		}
		else if (inc_len > 0) {
			last_chars[0] = last_chars[1];
			last_chars[1] = *ptr;
			ptr += inc_len;
			len -= inc_len;
		}
		else {
			php_mb_reset();
			ptr++;
			len--;
		}

Походу некоторые из этих наркоманов ещё и сидят на хабре :) Объясните хоть дураку, что не так. Сакральные исходники священного PHP нельзя критиковать?

Как бы вы переписали следующий код и почему?


check : if( this.status === 'check' ) {
    for( let master of this.masters ) {
        master.value()
        if( this.status !== 'check' ) break check
    }
    if( this.error ) throw this.error
    return this.cache
} 

this.status = 'compute'
const result = this.handler()
return this.push( result )
if(this.status === "check"){
  const breakCheckValue = this.masters.find(m => {
    m.value();
    return this.status !== "check";
  });

  if( this.error ) 
    throw this.error;

  if(breakCheckValue)
   return this.cache;
}

this.status = "compute";
const result = this.handler();
return this.push( result );


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

У this.masters нет find ибо это экземпляр Set. Ну и логика у вас сложная и не правильная получилась. Очевидно вы хотели воспользоваться более уместным some, а не find, но всё равно запутались в логических отрицаниях. К тому же проверка this.errors вдруг перестала зависеть от изменения this.status.


Ну а если никто в вашей команде не знает языка, на котором пишет, то у меня для вас плохие новости :-)

Ну чем является this.masters мне знать не откуда было, вам стоило отметить это. А так да не учёл зависимость проверки ошибки от изменения this.status и перепутал методы, косяк.
Напишу ещё раз:
if(this.status === "check" && allChecked(this.masters)) {
    if( this.error ) throw this.error;
    return this.cache;
}

this.status = "compute";
const result = this.handler();
return this.push( result );

function allChecked(masters){
  return [...masters].every(m => {
    m.value();
    return m.status === "check";
  });
}


Этот код на мой взгляд существенно чище чем ваш.

Замечательно, кода стало ещё больше и теперь нужно глазами мотать вверх-вниз чтобы понять что тут происходит. А происходит опять что-то не то, ибо вынося код в отдельную функцию вы разумно споткнулись на this.status и "починили" это заменив на m.status. Соответственно и функцию назвали в соответствии с тем, что она делает, а не с тем, что она должна делать.


Кроме того, выносом в отдельный метод вы увеличили размер стека процентов на 25%, ибо код этот находится в рекурсивно вызываемой функции. Тем самым вы увеличили риск получить ошибку переполнения стека, а также замусорили stack trace и flame chart, увеличив их вывод на те же 25%.


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

Мне очень нравится как вы меня постепенно посвящаете в контекст вашего кода, то masters вдруг Set, то функция часто вызывается, да ещё и рекурсивно… Я блин откуда это знаю!?

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

Вы бы вообще над дизайном класса подумали, почему например метод .value() меняет поля чужого класса? Может ему лучше возвращать данные и плевать исключения в случае ошибки, тогда вопросов как написать красивый код будет меньше?

Мотать глазами не надо, спокойно можно читать сверху вниз. (даже после фикса проверки поля status, const self = this;… self.staus === «checked» )

Кстати конвертацию Set`а в массив наверняка js движок оптимизирует и лишняя память не будет выделена. Вызов метода наверное заинлайнится, так что стек не вырастит.

Это же вы делаете необоснованные предположения, что this.masters — это масссив, а косвенная рекурсия невозможна. С чего вы это взяли?


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


Прыгать глазами надо ровно до тех пор, пока вы не отразите в её названии всё, что она делает. Что по длине будет сопоставимо с её исходным кодом.


Откуда такая уверенность, что компилятор сумеет развернуть вызов замыкания для каждого элемента из массива созданного из множества в итерирование по элементам множества?


Давайте подытожим...


Какие мы заимели проблемы:


  1. Код стал в полтора раза больше.
  2. Вместо нескольких простых условий появилось одно комплексное. Третее в ту же строку добавим?
  3. Добавилась новая функция с названием, не соответствующим действительности.
  4. Замедлилось время исполнения.
  5. Увеличилось потребление стека.
  6. Усложнились отладка (точки останова, исполнение по шагам, лишние элементы в стеке, просмотр значений переменных из разных скоупов)
  7. Усложнился профайлинг (увеличение графика, дробление "собственного" времени, сортировка по "собственному" времени).

Чего мы добились:


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

Оно того стоило?

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

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

Вот так можно:

if( this.status === 'check' ) {
    for( let master of this.masters ) {
        master.value()
        if( this.status !== 'check' ) break;
    }
    if( this.status === 'check' ) {
        if( this.error ) throw this.error
        return this.cache
    }
}
// ...


На одно сравнение со строкой больше — зато воспринимается проще.

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


check : if( this.status === 'check' ) {
    for( let master of this.masters ) {
        master.value()
        if( this.status === 'obsolete' ) break check
        if( this.status === 'sleep' ) break check
    }
    if( this.error ) throw this.error
    return this.cache
} 

Как вы измените свой?

Я не буду в своем коде делать усложняющий код рефакторинг.

Ок, это импрувмент/багфикс, а не рефакторинг. На вопрос-то ответите?

А вам в целом не кажется, что этот код (и тот что выше) какой-то мутный. Столько всего понамешано, даром, что строк мало. Тут и throw, и метки, и смена статуса по вызову value() (без знания кодовой базы вообще неочевидный момент), и какой-то cache. Мне кажется использование тут меток не является проблемой. Проблемой является вообще существование этого. Что это? Почему оно такое странное? Причина в производительности?

Это новая реализация вот этой библиотеки, с поддержкой квантификации вычислений: https://habrahabr.ru/post/317360/
Сможете реализовать менее "мутно" — я вам спасибо скажу :-)

function computedValue() {
    this.status = 'compute'
    const result = this.handler()
    return this.push(result)
}

function value() {
    if (this.status === 'check') {
        for (let master of this.masters) {
            master.value()
            if (this.status !== 'check') return computedValue()
        }
        if (this.error) throw this.error
        return this.cache
    }
    return computedValue()
}

Какие мы заимели проблемы:


  1. Код стал в полтора раза больше.
  2. Функция разделена на 2 без практической ценности (computedValue опасно вызывать где-либо, кроме как в value).
  3. Увеличилось потребление стека (опять же косвенная рекурсия).
  4. Усложнились отладка.
  5. Усложнился профайлинг.

Чего мы добились:


  1. Избавились от использования стандартной, но редко необходимой конструкции языка.

Оно того стоило?

1. Кода (без учёта скобок и пустых строк) добавилось всего одна строчка: объявление функции. Учтите, что я также явно добавил объявление function value(), которое в вашем коде было опущено.
2. computedValue() вполне может вызываться где-то ещё, где кэширование явно не нужно. Если её опасно отдавать пользователю, можно сделать её приватной.
Кроме того, в вашем варианте одна функция занимается двумя вещами: обслуживанием вычисления значения (выставление правильного статуса перед вызовом хэндлера, сохранением результата в правильном месте) и управлением кэшированием. Это нарушение SRP. Если в каком-то другом месте нужно будет вызвать хэндлер, нужно будет опять не забыть, какой выставить статус и куда девать результат.
3. У вас рекурсия без явного ограничения глубины? Т.е. кто-то может запросто устроить бесконечную рекурсию и его спасёт только стэк оверфлоу?
А вообще, тут хвостовой вызов, который скорее всего соптимизируется (tail call optimization).
4. Отладка упростилась, потому что для каждой функции можно написать отдельный юнит-тест, и отлаживать их в принципе не нужно будет. А если кто-то будет раскручивать стек, то по имени функции увидит, какой элемент функциональности выполнялся.
5. Не вижу усложнения.

2 — value()я не привёл так как приведённый код это далеко не всё её содержимое. В частности весь приведённый код завёрнут в try-catch для перехвата ошибок. Именно поэтому вызывать computedValue из других мест нельзя. Да и ситуаций, когда "кэширование явно не нужно" нету, ибо сам этот класс применяется, когда нужно кеширование. Вы делаете слишком далекоидущие выводы по обрывку кода.


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


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


5 — ну вот если бы вы им пользовались, то заметили бы следующие проблемы: увеличение высоты графика, дробление "собственного" времени на несколько функций и улёт этих функций вниз при сортировке по собственному времени.

3. return computedValue() — вот это вот хвостовой вызов и есть. Других форм вызова computedValue в моём коде нет, так что интерпретатор вправе оптимизировать его, например превратив вызов computedValue() в тот самый break to label который вы подставили руками.
Я, кстати, не спорю, что goto и break to label могут быть использованы для ручной оптимизации, равно как и ручная раскрутка стека и проч., если надеяться на оптимизатор не хочется.
Как тяжело с пришельцами из других языков…
В этом виноват WEB. Всем кто работает с WEB`ом приходится работать с js, нравится он им или нет. Альтернатив, пока нет :-\
Вот только здоровый человек, осваивая язык, читает спеку, а не надеется, что код из ранее изученного языка случайно окажется валидным и в этом языке.
Ну видимо перекочевало из C.
Можно чуть больше подробностей? Почему вы так думаете?

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

В дискуссии о вреде goto обычно никаких серьёзных минусов последнего, кроме некоего абстрактного «излишнего зашумления кода» не называется.
Если бы в Си не было goto, то чел, который писал функцию в интерпретаторе PHP из примера выше, напряг бы мозг и написал её нормально. Представьте, что другой наркоман решит добавить для функции php_mblen ещё одно возвращаемое значение: -3 или -1000. И вся эта жесть посыпется как карточный домик. В этом примере код с goto длинный, непонятный, потенциально глючный. Было бы интересно увидеть обратные примеры…
То что кто-то использует язык для написания корявых функций — вовсе не значит что операторы, которые он использует, глючные или не нужные.

Если бы в Си не было goto, то чел, который писал функцию в интерпретаторе PHP из примера выше, напряг бы мозг и написал её нормально.

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

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

код с goto длинный, непонятный

Ну, собственно, о чём я и говорил — никаких объективных минусов никто не называет.

потенциально глючный

А так вообще про огромное количество кода можно заявить, особенно если всякие оговорки к этому добавлять.
Судя по минусу в карму я понял, что goto — это ещё одна священная корова, о которой нельзя пренебрежительно отзываться на хабре :)

А в чем логика моего кода не соответствует исходному?
Минус ставил не я, если что.

А в чем логика моего кода не соответствует исходному?

Вы походу чуть ошиблись со скобочками. В оригинале:
		switch (inc_len) {
			case -2:
			case -1:
				inc_len = 1;
				php_mb_reset();
				break;
			case 0:
				goto quit_loop;
			case 1:
			default:
				last_chars[0] = last_chars[1];
				last_chars[1] = *ptr;
				break;
		}
		ptr += inc_len;
		len -= inc_len;
	}


ptr += inc_len;
len -= inc_len;

Находятся вне switch, т.е. будут исполнены в любом случае. В вашем же коде они исполнятся только если inc_len > 0
В этом и смысл.

Для inc_len == 0 эти два выражения как-раз не будут выполнены, потому что там goto, который выводит из цикла.

Для inc_len == -2 или -1 переменная inc_len устанавливается в 1, и я заменил эти два выражения на более эффективные: ptr++ и len--. Раз, уж, в исходном комментарии шла речь об эффективности.

Насчет минусов это не лично к вам претензия. Но судя потому, что люди минуснули, они не врубились в логику исходной функции с goto. Что подтверждает то, что в этом примере goto затрудняет понимание. Т.е. вариант без goto тут и понятней, и эффективнее, и короче, и менее глючный.
Об этом я уже писал. В индусском коде с goto скорее всего будет segmentation fault. Потому что будет выполнена ветка default. Она очевидно рассчитана только на положительные значения. При -100 всё сломается.

В моем же варианте любые отрицательные значения (-1, -2, -100), обозначающие что в строке что-то не найдено, интерпретируются одинаково. ptr увеличивается на единицу и поиск продолжается.

Конечно, в моем варианте логика немного изменилась. Но это просто исправление ошибки наркоманов-авторов PHP.
В индусском коде с goto скорее всего будет segmentation fault. Потому что будет выполнена ветка default. Она очевидно рассчитана только на положительные значения.

Не понимаю с чего вы это взяли.
default:
    last_chars[0] = last_chars[1];
    last_chars[1] = *ptr;
    break;

Отработает вполне корректно. Ведь если inc_len == -100, ещё не значит, что ptr отрицательный. Далее выполнится:
ptr += inc_len;
len -= inc_len;

И цикл может вполне закончится. В вашем же случае этого не произойдёт.

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

И я не пойму с чего вы так уверены, что ваши два эльса работают быстрее чем switch.
Цикл не завершится. ptr уменьшится на 100 и возможно станет отрицательным.

А len увеличится на 100! При том, что условие выхода из цикла len > 0. На следующей итерации либо php_mblen упадет с segmentation fault. Либо этот цикл будет вечным, потому что len всегда будет положительным.

Мой вариант работает быстрее потому, что

1) меньше сравнений, у меня 3 ветки (=0, >0, <0), а в варианте со switch 5 веток (-2, -1, 0, 1, default),

2) мои сравнения занимают меньше тактов процессора. Я уже 20 лет не писал на ассемблере, но на сколько я помню, сравнение с 0 или определение знака операнда быстрее, чем сравнение с -2, -1, 1.

3) в моем варианте для случая inc_len < 0 нет лишнего присваивания inc_len = 1, с последующим прибавлением и вычитанием. Вместо этого инкремент ptr++ и декремент len--. Которые так же занимают меньше тактов процессора, чем прибавление/вычитание.

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

Насчет изменения логики. Для inc_len >= -2 логика не изменилась. Она изменилась только для inc_len < -2 (чего как вы утверждаете быть не должно), что я сделал сознательно, чтобы исправить ошибку индусов.

Вы всё ещё настаиваете, что goto в данном случае — это хорошо? В этой теме по-моему я единственный смог понять как этот индусский код реально работает.
Ну для, чёт while(len > 0) я просмотрел, да.

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

Хотя я всегда считал что switch работает быстрее if, думаю без ассемблерного кода сравнивать бессмысленно. Собственно, что касается инкремента и декремента — тут вы вероятнее всего ошибаетесь. Подобная форма записи, насколько я знаю, даёт выигрыш, только если используется предикативная форма ++ptr, в противном случае, по скорости ptr++ эквивалентно ptr += inc_len если их формат совпадает.

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

Что касается goto, я нигде не писал, что «в данном случае» это хорошо. Я лишь сказал что есть случаи, в которых без него либо будет плохо, либо вообще не сделать. Просто потому что у меня в продакшене был код, который я пробовал написать и на if и на циклах и на goto. И самый читаемый вариант получился как раз на нём.

Всем этим я лишь хочу сказать, что goto это ровно такой же инструмент как и все остальные и пользоваться им надо с умом. А если генерить бредовый код, он всё равно останется бредовым, и не важно как он написан.
`++ptr` и `ptr++` в случае обычных указателей исполняются с одинаковой скоростью. Скорость префиксной и постфиксной формы инкремента может отличаться только для перегруженного оператора (и то если оптимизатор проморгает что возвращаемое значение не используется).
Почему? Чем для компилятора отличаются обычный и перегруженный ++?
довелось мне лет 15 назад поддерживать код на фортране (кажется 77), написанный немцами, связаться с которыми возможности не было.
он был весь на го ту.
тот ещё адок, уж поверьте.
Дык верю, только не факт что без goto его вообще можно было реализовать на фортране, не так ли?
согласен.
но с тех пор не люблю го ту и ненавижу фортран
Мне их кино нравится. Как их после этого не любить?!)

А минуса два:


  1. непредсказуемые переходы (не очень понятно, куда и при каких условиях перейдет управление). Особенно забавно, если goto в середину какой-нибудь функции прыгает. Не знаю, можно ли так в JS, но и проверять не буду
  2. непредсказуемый контекст. Вот прыгнуло исполнение в какое-то тридцатьтретье место. Переменные и константы теперь какие значения имеют? Надо ходить, смотреть, вспоминать.

При помощи конструкций break <label>; и continue <label>; можно только выходить из блоков, но не входить в них. Тут все же переходы остаются предсказуемыми, хоть и получаются страшно некрасивыми.

Это всего лишь один из способов «выстрелить себе в ногу», коих из без goto с десяток наберётся.
При помощи goto в Lua можно только выходить из блоков. А область видимости меток, емнип, так же блочная.
Моя первая программа на паскале выглядела так (извиняюсь за форматирование, я раньше о таком не знал):
program p1;
var a,b,k,l,d,e:integer;
c:boolean;
label l1;
begin
write('Type a, please ');
readln(a);
a:=(abs(a));
write('Type b, please ');
readln(b);
If b>0 then
       begin
k:=0;
l:=0;
repeat
d:= (a mod 10);
e:= (b mod 10);
c:=(d=e);
If C then
begin
k:=(d+k);
l:=(e+l);
a:=(a div 10);
b:=(b div 10);
end
else
begin
writeln('Не входит');
goto l1;
end;
until b=0;
Writeln('Входит');
l1:
       end
else writeln('Вы офигели!! Отрицательных цифр в записе числа не бывает!')
end.


Я до сих пор помню, как у меня управление скакало вниз-вверх, и это при том, что тут один-единственный goto. Да, тут плохое форматирование, плохое именование переменных и неоптимальность, окей. Но даже если бы это было, понять флоу в такой программе анрил. Я помню свои собственные ощущения. Глаза вверх-вниз вверх-вниз. И отладочная печать после каждой строчки (про watch я, конечно же, не знал).

Есть один известный дядька, он даже целый термин для этого goto придумал.
ну это полезно понимать, читая минифицированный код.
это почти никогда не надо, но знать полезно.
Знал об этом только потому, что просматриваю минифицированный код, а минификаторы как раз подобным штуками злоупотребляют.
заметка про запятую навела на мысль, но практического применения не смог найти
const a = (a, b) => (
a = a + b,
a * 2
)

Иногда так использую:
for(let i = 0; i < n; i++, j++) {
}

И не только в JS.
раз пошла такая пьянка, то for можно записать вообще как вызов одной функции — в таком случае цикл будет исполняться, пока функция не вернет false, undefined, null (или вообще ничего не вернет — тогда for завершится после первого прогона)

        for(; someAction(););  

        // просто для примера, данный код работоспособен
        function someAction() {
            someAction.i = someAction.i || 0; 
            console.log(someAction.i++);
            return someAction.i < 10;
        }
Так while же, зачем for? И выглядит понятнее.
я не зря сказал «раз пошла такая пьянка»
в for можно выполнять что угодно без тела цикла, но это ухудшает читаемость
то же самое относится к чудесам с запятой, описанным в статье — если внедрять такую практику, код станет заметно более плохим

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

но если нам нужен цикл со «сменным поведением», обороты вроде
while(doBehavior());
или
for(initBehavior();doBehavior();afterDoBehavior());
открывают целые бездны гибкой настройки кода, адаптивных алгоритмов и майндфаков

Если участок кода станет "горячим" — оптимизатор разберётся, если не станет — этих накладных расходов никто не заметит. Так что в угоду читаемости (а не так как в примере выше) можно.

Ну вот же как удобно отсортировать массив.
const a = [1,2,3,4,5];
for (let i = 0, j = a.length - 1, t; i < j; i++, j--) {
	t = a[i];
	a[i] = a[j];
	a[j] = t;
}
console.log(a);

Правда пришлось ввести дополнительную переменную. В го выглядит красивее, хотя синтаксис там несколько другой:
a := []int{1,2,3,4,5}
for i, j := 0, len(a) - 1; i < j; i, j = i +1, j - 1 {
	a[i], a[j] = a[j], a[i]
}
Запятая очень часто помогает упростить простые функции не теряя читабельность или делая лучше (именно простые функции!)

Из:
function toSortReverseArray(str){
str = str.split('');
str = sort();
return str.reverse();
}


В:
function toSortReverseArray(str){
return str = str.split(''), str.sort.reverse();
}


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

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


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

ого, спасибо за статью, я уже как раз хотел написать фреймворк, чтобы внедрить в JavaScript все эти возможности
Еще не поздно, а еще можно сделать приоритетным использование for in для массивов, без использования при этом hasOwnProperty. Или, к примеру, введя приоритетным создание массивов, объектов и так далее через new Array, new Object… А то что все эти литералы и литералы. Да и не только же для создания строк n-длины использовать, заполненных каким-то символом, с вызовом Array через new.
Про void function интересно, в остальном автор Америки не открыл (естественно, речь идёт про меня).
Пишу void 0 потому что подсвечивается лучше чем undefined. + защита от всяких undefined = true;
На счастье, это давным давно исправили. Но запись короче, да.
Пригодится для чтения обфусцированного кода, самому такое лучше не применять.
Интересный момент, что я в js никогда особо не углублялся, писал всякие мелкие расширения для хрома, но обо всём этом уже знал. Видимо сказывается, что изучал не «по порядку», а вырывая куски знаний под конкретные вещи :)
Мне, когда я была зеленым джуном, бывалый фронт-ендер не рекомендовал использовать метки. Мне в реальном коде не довелось их видеть. Может не случайно некоторые возможности языка не так и известны.
Нельзя использовать goto, а вот метки, чтобы выходить из внешнего цикла — вопрос открытый.
не использую
.querySelector
, пропущу почему, использую
.getElementsByTagName[tag][0]
,
.getElementsByClassName[class][0]
,
.getElementById(id)
и
.querySelectorAll
теми же средствами, хотя это не совсем к теме, скорее к примерам.

Да нет уж, вы не пропускайте и обоснуйте, почему пишете на js как в 2005 и считаете это правильным.

быстрее (погуглите тесты jsperf), отлавливают элементы созданные на лету, «не обертка», в нашем коллективе приводит к консенсусу чем выбирать элемент/массив/коллекцию, быстро конвертирует в библиотеку и прочее. Не торопитесь судить, я с вами в этом же году живу и опыт у меня с 2006 в вэб-разработке. Не считаю правильным поэтому написал использую а не используйте.

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

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

Передача аргументов в setTimeoutдоступна была не всегда, а, например, с IE9. Об этом как раз написано на MDN.

let personEl = document.querySelector('#person');
console.log(person.dataset)
В чем отличие personEl и person?
Да, видимо опечатка. А при отладке не заметили, потому что в браузере доступны глобальные переменные с именами по id у элементов. Т.е. для элемента с #person браузер автоматически создаёт глобальную переменную person.
для элемента с #person браузер автоматически создаёт глобальную переменную person.

поэтому и проскочила person. Спасибо.
Интересно когда понадобится делать let personEl = document.querySelector('#person'); если автоматом создается person?
Не во всех браузерах автоматом создается person?
Использовать глобальные переменные по id элементов плохая практика по двум причинам:

1. Потому что они глобальные. Если в вашем коде будет другая глобальная переменная person, то код, который работал с элементом #person, сломается.

2. Изначально не все браузеры это поддерживали. Первым был IE, остальные подтянулись для совместимости.

В общем не надо так делать. Знать об этом полезно, использовать — нет.
for (a = 0, b= 0; a < N; ++a, ++b) — и тут запятая в двух местах — вполне легитимная
if (let x = Something(), x > 0) — ограничим область видимости подобно переменной для for

for (var e = document.getElementById('smth'; e; e = e.parentNode) — итерация вверх по дому
if (let x = Something(), x > 0)

?
if (Something() > 0) 

В вашем варианте куда-то пропала переменная.
Так ведь в том и смысл, что не ясно для чего её вообще использовать в таком ключе.
Как это не ясно? Переменную можно использовать для чего угодно.

Ну вот вам более конкретный код:

if (let x = foo.indexOf(bar), x > -1) {
    bar.splice(x, 1);
}


Как вы здесь без переменной обойдетесь?

PS кстати, в каких версиях языка так можно делать?
Ой, да, действительно. Мой промах… Как в for же, один в один.
кстати, в каких версиях языка так можно делать?

Это хороший вопрос, учитывая что последний хром такое отказывается исполнять. Может a-tk скажет?

Вообще, эта конструкция, конечно, выглядит как что-то вредное с точки зрения читаемости. Я б такое не стал писать… Но пример забавный. Хотел написать «жаль, не рабочий», но нет, как-то и не жаль даже.
if (let x = Something(), x > 0)

Тут проблема в том, про при присвоении (операторы var, let и const), запятая интерпретируется как перечисление переменных:
let x = 1, y, z = 3;

И возможно только присвоение, иначе будет синтаксическая ошибка. Я к тому, что в принципе такой код невозможен:
let x = Something(), x > 0;
Между прочим, в C++17 он таки возможен (только вместо let надо auto поставить). Но им пришлось дополнять синтаксис операторов if и switch, оператор «запятая» тут и правда ни при чем.

Если такое написание станет популярным — может, и в Javascript его однажды добавят.
UFO just landed and posted this here
Я не понял, зачем автор говорил об атомарных операциях. Я не слышал ни об одном многопоточном рантайме. А с веб-воркерами нужно атомарность не в язык, а в апи хранения (типа indexed) добавлять.

Когда я был маленький и учил C++, я очень радовался, что там можно переопределить operator,() так, чтобы (x, y) обозначало скалярное произведение векторов x и y. Потому что всякие математики его часто именно так обозначают, а не точечкой и не треугольными скобочками. Правда, скобочки тут не нужны, но из-за приоритета всё равно нужны. P.S. не делайте так :/

Предлагаю хабру запретить анимированные картинки до ката.

void можно использовать для сокращения проверки на undefined:
if(typeof myVar=='undefined')

превращается в
if(myVar== void 0)


Правда, может усложнить читабельность тем, кто не знает про void (если в документе по стилю кода этот момент есть, то, как по мне, допустимо).

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

За доп. параметры setTimeout спасибо. Не уверен что стоит так писать с точки зрения понятность кода, но каких-то набросков вполне сгодится.
Не совсем верно, в данном случае лучше использовать "==="

const myVar = null;

myVar == void 0; // true

typeof myVar == 'undefined'; // false
Прочитал статью, понял что знаю о всех перечисленых особенностях. Теперь сижу и думаю хорошо это или плохо.
местами очень спорные конструкции. особенно запятая в условном плохо читается.
и с async както странно…
Sign up to leave a comment.