¿Son válidas las funciones del generador en la programación funcional?


17

Las preguntas son:

  • ¿Los generadores rompen el paradigma de programación funcional? ¿Por qué o por qué no?
  • En caso afirmativo, ¿se pueden usar generadores en la programación funcional y cómo?

Considera lo siguiente:

function * downCounter(maxValue) {
  yield maxValue;
  yield * downCounter(maxValue > 0 ? maxValue - 1 : 0);
}

let counter = downCounter(26);
counter.next().value; // 26
counter.next().value; // 25
// ...etc

El downCountermétodo parece apátrida. Además, llamar downCountercon la misma entrada siempre dará como resultado la misma salida. Sin embargo, al mismo tiempo, las llamadas next()no producen resultados consistentes.

No estoy seguro de si los generadores rompen o no el paradigma de programación funcional porque en este ejemplo counteres un objeto generador y, por next()lo tanto, la llamada produciría los mismos resultados que otro objeto generador creado con exactamente el mismo maxValue.

Además, llamar someCollection[3]a una matriz siempre devolvería el cuarto elemento. Del mismo modo, llamar next()cuatro veces a un objeto generador también siempre devolvería el cuarto elemento.

Para más contexto, estas preguntas se plantearon mientras trabajaba en un kata de programación . La persona que respondió a la pregunta, planteó la pregunta de si los generadores podrían usarse o no en la programación funcional y si tienen o no estado.


2
Cada programa tiene estado. La verdadera pregunta es si califica como estado funcional , que interpreto como "estado inmutable", estado que no cambia una vez que se asigna. Afirmo que la única forma en que puede hacer que un generador devuelva algo diferente en cada llamada es si el estado mutable está involucrado de alguna manera.
Robert Harvey

Respuestas:


14

Las funciones del generador no son particularmente especiales. Podemos implementar un mecanismo similar nosotros mismos reescribiendo la función del generador en un estilo basado en devolución de llamada:

function downCounter(maxValue) {
  return {
    "value": maxValue,
    "next": function () {
      return downCounter(maxValue > 0 ? maxValue - 1 : 0);
     },
  };
}

let counter = downCounter(26);
counter.value; //=> 26
counter.next().value; //=> 25

Claramente, el downCounteres tan puro y funcional como se pone. No hay problema aquí.

El protocolo generador utilizado por JavaScript involucra un objeto mutable. Esto no es necesario, consulte el código anterior. En particular, los objetos mutables significan que perdemos la transparencia referencial : la capacidad de reemplazar una expresión por su valor. Mientras que en mi ejemplo, counter.next().valueserá siempre evaluar a 25no importa donde se produce y con qué frecuencia lo repetimos, este no es el caso con el generador de JS - en un momento dado es 26, entonces 25, y lo que realmente podría ser cualquier número. Esto es problemático si pasamos una referencia al generador a otra función:

counter.next().value; //=> 25
otherFunction(counter); // does this consume the counter?
counter.next().value; // what will this be? It depends on the otherFunction()

Claramente, los generadores mantienen el estado y, por lo tanto, no son adecuados para la programación funcional "pura". Afortunadamente, no tiene que hacer programación funcional pura, y puede ser pragmático. Si los generadores aclaran su código, debe usarlos sin una mala conciencia. Después de todo, JavaScript no es un lenguaje funcional puro, a diferencia de, por ejemplo, Haskell.

Por cierto, en Haskell no hay diferencia entre devolver una lista y un generador, ya que utiliza una evaluación diferida:

downCounter :: Int -> [Int]
downCounter maxValue =
  maxValue : (downCounter (max 0 (maxValue - 1)))
-- invoke as "take n (downCounter 26)" to display n elements
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.