Есть ли механизм для цикла x раз в ES6 (ECMAScript 6) без изменяемых переменных?
типичный способ петли x
раз в JavaScript это:
for (var i = 0; i < x; i++)
doStuff(i);
но я не хочу использовать ++
оператор или иметь какие-либо изменяемые переменные вообще. Так есть ли способ, в ES6, чтобы цикл x
раз по-другому? Я люблю механизм Руби:
x.times do |i|
do_stuff(i)
end
что-нибудь подобное в JavaScript/ES6? Я мог бы обмануть и сделать свой собственный генератор:
function* times(x) {
for (var i = 0; i < x; i++)
yield i;
}
for (var i of times(5)) {
console.log(i);
}
конечно, я все еще использую i++
. По крайней мере, это вне поля зрения:), но я надеюсь, что есть лучший механизм в ES6.
15 ответов:
ОК!
приведенный ниже код написан с использованием синтаксиса ES6, но может быть так же легко написан в ES5 или даже меньше. ЕС6 составляет не требование создать "механизм для цикла x раз"
Если вам не нужен итератор вызов, это самая простая реализация
const times = x => f => { if (x > 0) { f() times (x - 1) (f) } } // use it times (3) (() => console.log('hi')) // or define intermediate functions for reuse let twice = times (2) // twice the power ! twice (() => console.log('double vision'))
Если вам нужен итератор, вы можете использовать именованную внутреннюю функцию с параметр счетчика для итерации для вас
const times = n => f => { let iter = i => { if (i === n) return f (i) iter (i + 1) } return iter (0) } times (3) (i => console.log(i, 'hi'))
прекратите читать здесь, Если вам не нравится учиться больше вещей ...
но что-то должно быть не так с ними...
- один филиал
if
отчетность некрасиво - что происходит на другой ветке ?- несколько операторов / выражений в телах функций -процедуры опасения смешиваются ?
- неявно возвращается
undefined
- индикация нечистой, побочной функцииразве нет лучшего способа ?"
есть. Давайте сначала вернемся к нашей первоначальной реализации
// times :: Int -> (void -> void) -> void const times = x => f => { if (x > 0) { f() // has to be side-effecting function times (x - 1) (f) } }
конечно, это просто, но обратите внимание, как мы просто называем
f()
и ничего с этим не делай. Это действительно ограничивает тип функции, которую мы можем повторить несколько раз. Даже если мы есть итератор доступен,f(i)
не намного более универсален.что, если мы начнем с лучшей процедуры повторения функций ? Может быть, что-то, что лучше использует вход и выход.
повтор общей функции
// repeat :: forall a. Int -> (a -> a) -> a -> a const repeat = n => f => x => { if (n > 0) return repeat (n - 1) (f) (f (x)) else return x } // power :: Int -> Int -> Int const power = base => exp => { // repeat <exp> times, <base> * <x>, starting with 1 return repeat (exp) (x => base * x) (1) } console.log(power (2) (8)) // => 256
выше мы определили общий
repeat
функция, которая принимает дополнительный вход, который используется для запуска повторного применения одного функция.// repeat 3 times, the function f, starting with x ... var result = repeat (3) (f) (x) // is the same as ... var result = f(f(f(x)))
реализация
times
Сrepeat
Ну это сейчас легко; почти вся работа уже сделана.
// repeat :: forall a. Int -> (a -> a) -> a -> a const repeat = n => f => x => { if (n > 0) return repeat (n - 1) (f) (f (x)) else return x } // times :: Int -> (Int -> Int) -> Int const times = n=> f=> repeat (n) (i => (f(i), i + 1)) (0) // use it times (3) (i => console.log(i, 'hi'))
так как наша функция принимает
i
в качестве входных данных и возвращаетi + 1
, это эффективно работает как наш итератор, который мы переходим кf
каждый раз.мы исправили наш список вопросов пули тоже
- нет больше уродливых одна ветка
if
заявления- тела с одним выражением указывают на хорошо разделенные проблемы
- нет больше бесполезно, неявно возвращается
undefined
оператор запятой JavaScript, the
в случае, если у вас возникли проблемы с тем, как работает последний пример, это зависит от вашего понимания одного из старейших боевых топоров JavaScript;оператор запятая - короче говоря, он оценивает выражения слева направо и возвращает значение последнего вычисленного выражения.
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
в нашем примере выше, я использую
(i => (f(i), i + 1))
это просто краткий способ записи
(i => { f(i); return i + 1 })
Оптимизация Хвостового Вызова
как сексуально, как рекурсивные реализации, на данный момент было бы безответственно для меня рекомендовать их, учитывая, что нет JavaScript VM я могу подумайте о поддержке правильного устранения хвостового вызова-babel использовал его для транспилирования, но он был в состоянии "сломан; будет переопределен" уже более года.
repeat (1e6) (someFunc) (x) // => RangeError: Maximum call stack size exceeded
таким образом, мы должны пересмотреть наши реализации
repeat
чтобы сделать его безопасным для стека.код тут использовать изменяемые переменные
n
иx
но обратите внимание, что все мутации локализованы вrepeat
функция - никакие изменения состояния (мутации) не видны снаружи функция// repeat :: Int -> (a -> a) -> (a -> a) const repeat = n => f => x => { let m = 0, acc = x while (m < n) (m = m + 1, acc = f (acc)) return acc } // inc :: Int -> Int const inc = x => x + 1 console.log (repeat (1e8) (inc) (0)) // 100000000
это будет иметь много вы говорите "но это не функционально !- Я знаю, Просто расслабься. Мы можем реализовать Clojure-стиль
loop
/recur
интерфейс для постоянного пространства цикла с помощью чистыми выражениями; неwhile
вещи.здесь мы абстрагируемся
while
Сloop
функция-она ищет специальныйrecur
тип, чтобы сохранить цикл работает. Когда неrecur
тип обнаруженный цикл завершен, и результат вычисления возвращаетсяconst recur = (...args) => ({ type: recur, args }) const loop = f => { let acc = f () while (acc.type === recur) acc = f (...acc.args) return acc } const repeat = $n => f => x => loop ((n = $n, acc = x) => n === 0 ? acc : recur (n - 1, f (acc))) const inc = x => x + 1 const fibonacci = $n => loop ((n = $n, a = 0, b = 1) => n === 0 ? a : recur (n - 1, b, a + b)) console.log (repeat (1e7) (inc) (0)) // 10000000 console.log (fibonacci (100)) // 354224848179262000000
используя ES2015 Spread operator:
[...Array(n)].map()
const res = [...Array(10)].map((_, i) => { return i * 10; }); // as a one liner const res = [...Array(10)].map((_, i) => i * 10);
или если вам не нужен результат:
[...Array(10)].forEach((_, i) => { console.log(i); }); // as a one liner [...Array(10)].forEach((_, i) => console.log(i));
обратите внимание, что если вам просто нужно повторить строку, вы можете использовать строку.прототип.повторите.
console.log("0".repeat(10)) // 0000000000
Я думаю, что лучшее решение-использовать
let
:for (let i=0; i<100; i++) …
что создаст новый (изменяемый)
i
переменная для каждой оценки тела и гарантирует, чтоi
изменяется только в выражении инкремента в этом синтаксисе цикла, а не где-либо еще.я мог бы обмануть и сделать свой собственный генератор. По крайней мере
i++
находится вне поля зрения :)этого должно быть достаточно, ИМО. Даже в чистых языках все операции (или при по крайней мере, их интерпретаторы) построены из примитивов, которые используют мутацию. Пока он правильно определен, я не вижу, что в этом плохого.
вы должны быть в порядке с
function* times(n) { for (let i = 0; i < x; i++) yield i; } for (const i of times(5)) console.log(i);
но я не хочу использовать
++
оператор или иметь какие-либо изменяемые переменные вообще.тогда ваш единственный выбор-использовать рекурсию. Вы можете определить эту функцию генератора без изменяемого
i
а также:function* range(i, n) { if (i >= n) return; yield i; return yield* range(i+1, n); } times = (n) => range(0, n);
но это кажется излишним для меня и может иметь проблемы с производительностью (поскольку устранение хвостового вызова недоступно для
return yield*
).
Ответ: 09 Декабря 2015
лично я нашел принятый ответ как кратким (хорошим), так и кратким (плохим). Оцените это утверждение может быть субъективным, поэтому, пожалуйста, прочитайте этот ответ и посмотрите, согласны ли вы или нет
пример, приведенный в вопросе было что-то вроде Руби:
x.times do |i| do_stuff(i) end
выражение этого в JS с помощью ниже позволит:
times(x)(doStuff(i));
здесь код:
let times = (n) => { return (f) => { Array(n).fill().map((_, i) => f(i)); }; };
вот именно!
простой пример использования:
let cheer = () => console.log('Hip hip hooray!'); times(3)(cheer); //Hip hip hooray! //Hip hip hooray! //Hip hip hooray!
альтернативно, следуя примерам принятого ответа:
let doStuff = (i) => console.log(i, ' hi'), once = times(1), twice = times(2), thrice = times(3); once(doStuff); //0 ' hi' twice(doStuff); //0 ' hi' //1 ' hi' thrice(doStuff); //0 ' hi' //1 ' hi' //2 ' hi'
Примечание - определение функции
аналогичный / связанный вопрос, который использует принципиально очень похожие конструкции кода, может быть, есть удобная функция диапазона в (core) JavaScript, что-то похожее на диапазон подчеркивания функция.
создать массив из n чисел, начиная с x
подчеркивание
_.range(x, x + n)
ES2015
пару альтернатив:
Array(n).fill().map((_, i) => x + i) Array.from(Array(n), (_, i) => x + i)
демо с использованием n = 10, x = 1:
> Array(10).fill().map((_, i) => i + 1) // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] > Array.from(Array(10), (_, i) => i + 1) // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
в быстром тесте я побежал, причем каждый из вышеперечисленных работает миллион раз каждый, используя наше решение и функцию doStuff, первый подход (Array(n).заполнение ()) доказано чуть быстрее.
const times = 4; new Array(times).fill().map(() => console.log('test'));
этот фрагмент будет
console.log
test
4 раза.
Не то, что я бы учил (или когда-либо использовал в своем коде), но вот достойное codegolf решение без мутации переменной, нет необходимости в ES6:
Array.apply(null, {length: 10}).forEach(function(_, i){ doStuff(i); })
на самом деле это скорее интересное доказательство концепции, чем полезный ответ.
Array(100).fill().map((_,i)=> console.log(i) );
эта версия удовлетворяет требование OP о неизменности. Также рассмотрите возможность использования
reduce
вместоmap
в зависимости от вашего варианта использования.это также вариант, если вы не возражаете немного мутации в вашем прототипе.
Number.prototype.times = function(f) { return Array(this.valueOf()).fill().map((_,i)=>f(i)); };
теперь мы можем сделать это
((3).times(i=>console.log(i)));
+1 к аркселдону за
.fill
предложение.
насколько мне известно, не существует никакого механизма в ЕС6, похожими на Руби
times
метод. Но вы можете избежать мутации с помощью рекурсии:let times = (i, cb, l = i) => { if (i === 0) return; cb(l - i); times(i - 1, cb, l); } times(5, i => doStuff(i));
в функциональной парадигме
repeat
обычно является бесконечной рекурсивной функцией. Чтобы использовать его, нам нужна либо ленивая оценка, либо стиль прохождения продолжения.ленивое вычисленное повторение функции
const repeat = f => x => [x, () => repeat(f) (f(x))]; const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f()); console.log( take(8) (repeat(x => x * 2) (1)) // 256 );
я использую thunk (функция без аргументов) для достижения ленивой оценки в Javascript.
повторение функции с продолжением проходя стиль
const repeat = f => x => [x, k => k(repeat(f) (f(x)))]; const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1)); console.log( take(8) (repeat(x => x * 2) (1)) // 256 );
хп-это сначала было немного страшно. Однако он всегда следует одной и той же схеме: последний аргумент является продолжением (функцией), которая вызывает свое собственное тело:
k => k(...)
. Обратите внимание, что Роспотребнадзор выворачивает приложение наизнанку, т. е.take(8) (repeat...)
становитсяk(take(8)) (...)
здесьk
частично применяетсяrepeat
.вывод
разделение повторения (
repeat
) из условия расторжения договора (take
) мы приобретаем гибкость-разъединение забот до своего горького конца : D
если вы готовы использовать библиотеку, там же лодашь
_.times
или подчеркивание_.times
:_.times(x, i => { return doStuff(i) })
обратите внимание, что это возвращает массив результатов, так что это действительно больше похоже на этот ruby:
x.times.map { |i| doStuff(i) }
обращаясь к функциональному аспекту:
function times(n, f) { var _f = function (f) { var i; for (i = 0; i < n; i++) { f(i); } }; return typeof f === 'function' && _f(f) || _f; } times(6)(function (v) { console.log('in parts: ' + v); }); times(6, function (v) { console.log('complete: ' + v); });
генераторы? Рекурсия? почему так много ненависти на мутации? ; -)
Если это приемлемо, пока мы "скрываем" его, то просто примите использование унарного оператора, и мы можем сохранить вещи простыми:
Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }
Так же, как в ruby:
> (3).times(console.log) 0 1 2
преимущества этого решения
- простой для чтения / использования (ИМО)
- возвращаемое значение может быть использовано как сумма, или просто проигнорировано
- простая версия es6, также ссылка на версия TypeScript код
недостатки - Мутация. Будучи внутренним, только мне все равно, может быть, некоторые другие тоже не будут.
примеры и код
times(5, 3) // 15 (3+3+3+3+3) times(5, (i) => Math.pow(2,i) ) // 31 (1+2+4+8+16) times(5, '<br/>') // <br/><br/><br/><br/><br/> times(3, (i, count) => { // name[0], name[1], name[2] let n = 'name[' + i + ']' if (i < count-1) n += ', ' return n }) function times(count, callbackOrScalar) { let type = typeof callbackOrScalar let sum if (type === 'number') sum = 0 else if (type === 'string') sum = '' for (let j = 0; j < count; j++) { if (type === 'function') { const callback = callbackOrScalar const result = callback(j, count) if (typeof result === 'number' || typeof result === 'string') sum = sum === undefined ? result : sum + result } else if (type === 'number' || type === 'string') { const scalar = callbackOrScalar sum = sum === undefined ? scalar : sum + scalar } } return sum }
версия TypeScipt
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011