tutorial español definicion javascript generator ecmascript-6 ecmascript-harmony

javascript - español - ¿Existe un mecanismo para repetir x veces en ES6(ECMAScript 6) sin variables mutables?



javascript español (15)

¡OKAY!

El siguiente código está escrito con sintaxis de ES6, pero podría escribirse con la misma facilidad en ES5 o incluso menos. ES6 no es un requisito para crear un "mecanismo para repetir x veces"

Si no necesita el iterador en la devolución de llamada , esta es la implementación más simple

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''))

Si necesita el iterador , puede usar una función interna con nombre con un parámetro de contador para iterar por usted

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''))

Deja de leer aquí si no te gusta aprender más cosas ...

Pero algo debería sentirse mal sobre esos ...

  • rama única if declaraciones son feas, ¿qué sucede en la otra rama?
  • múltiples declaraciones / expresiones en los cuerpos de la función: ¿ se mezclan las inquietudes del procedimiento?
  • devuelto implícitamente undefined - indicación de función impura, efecto secundario

"¿No hay una mejor manera?"

Ahi esta. Primero revisemos nuestra implementación inicial

// times :: Int -> (void -> void) -> void const times = x => f => { if (x > 0) { f() // has to be side-effecting function times (x - 1) (f) } }

Claro, es simple, pero observe cómo simplemente llamamos f() y no hacemos nada con él. Esto realmente limita el tipo de función que podemos repetir varias veces. Incluso si tenemos el iterador disponible, f(i) no es mucho más versátil.

¿Qué pasa si comenzamos con un mejor tipo de procedimiento de repetición de funciones? Tal vez algo que haga un mejor uso de la entrada y la salida.

Función genérica de repetición

// 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

Arriba, definimos una función de repeat genérica que toma una entrada adicional que se utiliza para iniciar la aplicación repetida de una sola función.

// 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)))

Implementando times con repeat

Bueno, esto es fácil ahora; Casi todo el trabajo ya está hecho.

// 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''))

Dado que nuestra función toma i como entrada y devuelve i + 1 , efectivamente funciona como nuestro iterador que pasamos a f cada vez.

También hemos solucionado nuestra lista de problemas.

  • No más ramas feas if declaraciones
  • Los cuerpos de expresión única indican preocupaciones bien separadas
  • No más inútil, devuelto implícitamente undefined

Operador de coma JavaScript, el

En caso de que tenga problemas para ver cómo funciona el último ejemplo, depende de su conocimiento de uno de los ejes de batalla más antiguos de JavaScript; el operador de coma : en resumen, evalúa las expresiones de izquierda a derecha y devuelve el valor de la última expresión evaluada

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

En nuestro ejemplo anterior, estoy usando

(i => (f(i), i + 1))

que es solo una forma sucinta de escribir

(i => { f(i); return i + 1 })

Tail Call Optimization

A pesar de lo sexy que son las implementaciones recursivas, en este punto sería irresponsable para mí recomendarlas, dado que ninguna máquina virtual JavaScript en la que pueda pensar admite la eliminación adecuada de llamadas de cola: Babel solía transpilarla, pero se ha roto, se volverá a implementar "estado por más de un año.

repeat (1e6) (someFunc) (x) // => RangeError: Maximum call stack size exceeded

Como tal, deberíamos volver a visitar nuestra implementación de repeat para que sea apilable.

El siguiente código utiliza variables mutables n y x pero tenga en cuenta que todas las mutaciones están localizadas en la función de repeat : no hay cambios de estado (mutaciones) visibles desde el exterior de la función

// 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

Esto hará que muchos de ustedes digan "¡pero eso no es funcional!" - Lo sé, solo relájate. Podemos implementar una interfaz de recur / loop estilo Clojure para loop de espacio constante utilizando expresiones puras ; nada de eso while cosas.

Aquí abstraemos while estamos fuera con nuestra función de loop : busca un tipo recur especial para mantener el bucle en ejecución. Cuando se encuentra un tipo no recur , el ciclo finaliza y se devuelve el resultado del cálculo.

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

La forma típica de recorrer x veces en JavaScript es:

for (var i = 0; i < x; i++) doStuff(i);

Pero no quiero usar el operador ++ ni tener ninguna variable mutable. Entonces, ¿hay alguna forma, en ES6, de recorrer x veces de otra manera? Me encanta el mecanismo de Ruby:

x.times do |i| do_stuff(i) end

¿Algo similar en JavaScript / ES6? Podría hacer trampa y hacer mi propio generador:

function* times(x) { for (var i = 0; i < x; i++) yield i; } for (var i of times(5)) { console.log(i); }

Por supuesto, todavía estoy usando i++ . Al menos está fuera de la vista :), pero espero que haya un mejor mecanismo en ES6.


Afaik, no hay ningún mecanismo en ES6 similar al método de times de Ruby. Pero puede evitar la mutación usando la recursividad:

let times = (i, cb, l = i) => { if (i === 0) return; cb(l - i); times(i - 1, cb, l); } times(5, i => doStuff(i));

Demostración: http://jsbin.com/koyecovano/1/edit?js,console


Creo que es bastante simple:

[...Array(3).keys()]

o

Array(3).fill()


Creo que la mejor solución es usar let :

for (let i=0; i<100; i++) …

Eso creará una nueva variable (mutable) i para cada evaluación del cuerpo y asegura que el i solo se cambie en la expresión de incremento en esa sintaxis de bucle, no de ningún otro lado.

Podría hacer trampa y hacer mi propio generador. Al menos i++ está fuera de la vista :)

Eso debería ser suficiente imo. Incluso en lenguajes puros, todas las operaciones (o al menos, sus intérpretes) se construyen a partir de primitivas que usan mutación. Mientras tenga el alcance adecuado, no puedo ver qué hay de malo en eso.

Deberías estar bien con

function* times(n) { for (let i = 0; i < x; i++) yield i; } for (const i of times(5)) console.log(i);

Pero no quiero usar el operador ++ ni tener ninguna variable mutable.

Entonces su única opción es usar la recursividad. También puede definir esa función de generador sin un mutable i :

function* range(i, n) { if (i >= n) return; yield i; return yield* range(i+1, n); } times = (n) => range(0, n);

Pero eso me parece excesivo y podría tener problemas de rendimiento (ya que la eliminación de llamadas de cola no está disponible para el return yield* ).


En el paradigma funcional, la repeat suele ser una función recursiva infinita. Para usarlo necesitamos una evaluación perezosa o un estilo de pase de continuación.

Lazy evaluó la repetición de funciones

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 );

Utilizo un thunk (una función sin argumentos) para lograr una evaluación perezosa en Javascript.

Repetición de funciones con estilo de paso continuo

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 );

CPS da un poco de miedo al principio. Sin embargo, siempre sigue el mismo patrón: el último argumento es la continuación (una función), que invoca su propio cuerpo: k => k(...) . Tenga en cuenta que CPS convierte la aplicación al revés, es decir, take(8) (repeat...) convierte en k(take(8)) (...) donde k es la repeat parcialmente aplicada.

Conclusión

Al separar la repetición ( repeat ) de la condición de terminación ( take ) obtenemos flexibilidad, separación de preocupaciones hasta su amargo final: D


Generadores? Recursividad? ¿Por qué tanto odio a la mutatina? ;-)

Si es aceptable siempre que lo "ocultemos", simplemente acepte el uso de un operador unario y podemos simplificar las cosas :

Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }

Como en rubí:

> (3).times(console.log) 0 1 2


No es algo que enseñe (o use en mi código), pero aquí hay una solución digna de codegolf sin mutar una variable, sin necesidad de ES6:

((3).times(i=>console.log(i)));

Más una cosa interesante de prueba de concepto que una respuesta útil, realmente.


Si está dispuesto a usar una biblioteca, también hay lodash _.times o subrayado _.times :

_.times(x, i => { return doStuff(i) })

Tenga en cuenta que esto devuelve una serie de resultados, por lo que es realmente más parecido a este rubí:

x.times.map { |i| doStuff(i) }


Usando el operador ES2015 Spread :

[...Array(n)].map()

const res = [...Array(10)].map((_, i) => { return i * 10; }); // as a one liner const res = [...Array(10)].map((_, i) => i * 10);

O si no necesitas el resultado:

[...Array(10)].forEach((_, i) => { console.log(i); }); // as a one liner [...Array(10)].forEach((_, i) => console.log(i));

O utilizando el operador ES2015 Array.from :

Array.from(...)

const res = Array.from(Array(10)).map((_, i) => { return i * 10; }); // as a one liner const res = Array.from(Array(10)).map((_, i) => i * 10);

Tenga en cuenta que si solo necesita una cadena repetida, puede usar String.prototype.repeat .

console.log("0".repeat(10)) // 0000000000


abordando el aspecto funcional:

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); });


Respuesta: 09 de diciembre de 2015

Personalmente, encontré la respuesta aceptada tanto concisa (buena) como concisa (mala). Apreciamos que esta declaración puede ser subjetiva, así que lea esta respuesta y vea si está de acuerdo o en desacuerdo

El ejemplo dado en la pregunta fue algo así como el de Ruby:

times(x)(doStuff(i));

Expresar esto en JS usando a continuación permitiría:

let times = (n) => { return (f) => { Array(n).fill().map((_, i) => f(i)); }; };

Aquí está el código:

let cheer = () => console.log(''Hip hip hooray!''); times(3)(cheer); //Hip hip hooray! //Hip hip hooray! //Hip hip hooray!

¡Eso es!

Ejemplo de uso simple:

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''

Alternativamente, siguiendo los ejemplos de la respuesta aceptada:

_.range(x, x + n)

Nota al margen: definición de una función de rango

Una pregunta similar / relacionada, que utiliza construcciones de código fundamentalmente muy similares, podría ser si existe una función conveniente de Rango en JavaScript (núcleo), algo similar a la función de rango de subrayado.

Cree una matriz con n números, comenzando desde x

Guion bajo

Array(n).fill().map((_, i) => x + i) Array.from(Array(n), (_, i) => x + i)

ES2015

Par de alternativas:

> 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 ]

Demostración usando n = 10, x = 1:

const times = 4; new Array(times).fill().map(() => console.log(''test''));

En una prueba rápida que ejecuté, cada una de las anteriores ejecutándose un millón de veces usando nuestra solución y la función doStuff, el enfoque anterior (Array (n) .fill ()) resultó un poco más rápido.


Ventajas de esta solución

  • Más simple de leer / usar (imo)
  • El valor de retorno puede usarse como una suma o simplemente ignorarse
  • Versión es6 simple, también enlace a la versión TypeScript del código

Desventajas - Mutación. Siendo interno solo no me importa, tal vez algunos otros tampoco.

Ejemplos y código

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 versión
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011


for (let i of Array(100).keys()) { console.log(i) }


x.times do |i| do_stuff(i) end

Este fragmento console.log test 4 veces.


Array.apply(null, {length: 10}).forEach(function(_, i){ doStuff(i); })

Esta versión satisface los requisitos de OP para la inmutabilidad. También considere usar reduce lugar de map dependiendo de su caso de uso.

Esta también es una opción si no te importa un poco de mutación en tu prototipo.

Array(100).fill().map((_,i)=> console.log(i) );

Ahora podemos hacer esto

Number.prototype.times = function(f) { return Array(this.valueOf()).fill().map((_,i)=>f(i)); };

+1 a arcseldon para la sugerencia .fill .