Pull to refresh

Comments 21

UFO just landed and posted this here
Полностью согласен с комментарием, но думаю стоит раскрыть смысл сказанного, чтобы дочитавшие до комментариев смогли оценить весь вред написанного в статье подхода. Код приведенный выше порождает рекурсию, а значит каждая итерация подобного цикла поедает память помещая значения переменных в стек для вызова самой себя с новыми параметрами. Итого, при обработке 100 элементов в памяти может оказать 100 копий указателя на массив list, newList, значение инкрементируемой нами переменной i, указатель на функцию проверки predicate плюс системная информация создающая пространство имен для вызова новой функции.

    return isDone  ? newList :
           isMatch ? takeFirst(limit - 1, predicate, list, i + 1, [...newList, list[i]]) // <<== вот тут рекурсивный вызов
                   : takeFirst(limit, predicate, list, i + 1, newList) // <<== и тут


Константы isDone и isMatch также будут помещены в стек при каждом рекурсивном вызове, так как они каждый раз будут являться частью нового пространства имен.

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

function takeFirst(limit, predicate, list, i = 0, newList = []) {
	if (i >= 0 && i < list.length && newList.length < limit) {
		if (predicate(list[i])) {
			newList.push(list[i])
		}
		takeFirst(limit, predicate, list, i + 1, newList) // <<== РЕКУРСИЯ!!!!
	}
	return newList
}

var arr = [1, 2, 2, 3, 3, 2, 4, 5, 2, 2, 2, 2]
var isTwo = function (value) {
	return value == 2
}

console.log(takeFirst(5, isTwo, arr))

Движок должен допускать хвостовую рекурсию. Если для него это не гарантируется, использовать подобные приёмы нежелательно.
Пора придумать пометку «красивый код», который будет предупреждать солдата-разработчика, что это не боевые доспехи, а парадные. Последнее время так много говорят о получении элемента array[5] при помощи трехкратного клонирования, что это отдается эхом на всех форумах. И вот представьте картину, пришел новичок, который тольком не понимает что такое переменная и ему говорят что вот так нужно делать. Он верит и учится, а затем приходит на работу и встречает узкое место в коде. Что будет? А вспомните себя в самом начале, циклы и операции с массивами были всегда самыми сложными, после событий. Вот и получится, что уже джуниора нужно будет учить циклам, хотя должно быть все наоборот.
Да и выглядящие красиво операции с массивами, в совокупности + функциональное программирование, могут и наверняка приводят к тому, что js приложения на мобильных все ругают за то что они «плывут», а на десктопе приводит к тому, что пару вкладок жрут памяти, как современная игра. И это в то время, когда сложность и интерактивность приложений растет, а развитие машин замедляется.
Наивные функциональщики думают, что array.filter/array.slice реализован через каррирование))

Строго говоря, для новичка будет достаточно остановиться на
const result = cats.filter(isKitten).slice(0, 5);

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

Писать ради этого for и уж тем более каррирование — глупость и баловство, да.

Какая-то странная статья. Да, лучше обходиться без break, но для этого не обязательно нужно писать такой наркоманский код, как в функции takeFirst.


Достаточно просто вынести for-цикл в отдельную функцию, и заменить break на return


function firstKittens(cats, maxNumber) {
  var result = []
  // старый добрый for
  for (var i = 0; i < cats.length; i++) {
    if (isKitten(cats[i])) {
      result.push(cats[i])
      if (result.length >= maxNumber) {
        return result;
      }
    }
  }
  return result;
}
заменить break на return

очевидно, автор напишет: return это GOTO функций и его следует избегать!
Запрет break, continue, и return не последним оператором — это норма для «самого строгого» варианта принципов структурного программирования. Не слышал автора по этому поводу, но вообще такие нормы есть.

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

Но, всё же важнее выполнение конечной цели всех таких ограничений — надёжный, удобочитаемый код.
Два return в функции на 15 строк, как мне кажется, понятнее, чем 5 дополнительных флагов.
Прочитал статью, еще больше полюбил цикл for (и goto).
Но это не очень функционально

И что? ФП — не серебряная пуля, хоть автор и старается нам это так приподнести.

– ухудшилась читаемость кода;
– производительность снизилась.


А в чём плюсы описанного подхода?

Вот начальная функция с for, но без break и лишних return:


const takeFirst = (limit, predicate, list) => {
  const newList = []

  for (var i = 0; i < list.length && newList.length < limit; i++) {
    if (predicate(list[i])) {
      newList.push(list[i]);
    }
  }

  return newList
}
/*
 * takeFirst работает как `filter`, но поддерживает ограничение.
 *
 * @param {number} limit - Максимальное количество возвращаемых соответствий
 * @param {function} predicate - Функция соответствия, принимает item и возвращает true или false
 * @param {array} list - Список, который будет отфильтрован
 * @param {number} [i] - Индекс, с которого начать фильтрацию (по умолчанию 0)
 */
const takeFirst = (limit, predicate, list, i = 0, newList = []) => {
    const isDone = limit <= 0 || i >= list.length
    const isMatch = isDone ? undefined : predicate(list[i])

    return isDone  ? newList :
           isMatch ? takeFirst(limit - 1, predicate, list, i + 1, [...newList, list[i]])
                   : takeFirst(limit, predicate, list, i + 1, newList)
}

Открой меня!


Но ведь это же


Joel Thoms
Computer Scientist and Technology Evangelist with 21 years of experience with JavaScript!

Мы должны прислушаться к его словам ;)

UFO just landed and posted this here
Это потому, что у вас нет бороды, вы не ходите в барбершоп и не пьёте утренний смузи, по утрам не читаете любимую газету «MainStream»
Странно как-то это все, если вам нужен цикл с выходом по условию, и вы не любите break, используйте while. Или с ним тоже какие-то заморочки?

УчОные всего мира встали и пошли улучшать JS. С нетерпением жду ещё одну историю из серии "сложно о простом"

Это похоже на утверждение «используйте один единственный return в функциях». В итоге получается абсолютно нечитаемая лапша.
Юзаю break и continue и ни разу не сталкивался с какими-либо проблемами.
Так и не понял, чем break; не угодил? Наговнокодить можно и с помощью любых других инструментов языка, что кстати отлично показано в итоговом решении от которого глаза кровоточат.

Более того, даже GOTO можно и нужно использовать, если умеете это делать без вреда для окружающих.

Вы же пользуетесь арифметикой? Может тоже объявим ее злом? Например, 42<<4 == 42*Math.pow(2,4), удобно, правда? И главное быстро! Math.pow — зло! Нужно использовать побитовый сдвиг!

Да, я понимаю, что Joel Thoms начинал изучать JS когда я начинал изучать горшок, но горшок остался тот же, а вот JS развивается и подобная ересь устарела лет на 10 уже.
Sign up to leave a comment.

Articles