En programación funcional , un iteratee es una abstracción componible para procesar incrementalmente fragmentos de datos de entrada presentados secuencialmente de una manera puramente funcional . Con iteraciones, es posible transformar de manera perezosa la forma en que un recurso emitirá datos, por ejemplo, convirtiendo cada fragmento de la entrada a mayúsculas a medida que se recuperan o limitando los datos a solo los cinco primeros fragmentos sin cargar todos los datos de entrada en memoria. Los iteratees también son responsables de abrir y cerrar recursos, proporcionando una gestión de recursos predecible.
En cada paso, a un iteratee se le presenta uno de los tres posibles tipos de valores: el siguiente fragmento de datos, un valor para indicar que no hay datos disponibles o un valor para indicar que el proceso de iteración ha finalizado. Puede devolver uno de los tres tipos posibles de valores, para indicar a la persona que llama lo que se debe hacer a continuación: uno que significa "detener" (y contiene el valor de retorno final), uno que significa "continuar" (y especifica cómo continuar) , y uno que significa "señalar un error". Los últimos tipos de valores representan en efecto los posibles "estados" de un iteratee. Un iteratee normalmente comenzaría en el estado "continuar".
Los iterados se utilizan en Haskell y Scala (en Play Framework [1] y en Scalaz ), y también están disponibles para F # . [2] Existen varias implementaciones ligeramente diferentes de iterados. Por ejemplo, en el marco de Play, involucran Futures para que se pueda realizar el procesamiento asincrónico.
Debido a que los iterados son llamados por otro código que los alimenta con datos, son un ejemplo de inversión de control . Sin embargo, a diferencia de muchos otros ejemplos de inversión de control, como el análisis sintáctico de XML SAX , el iteratee retiene una cantidad limitada de control sobre el proceso. No puede revertir y mirar datos anteriores (a menos que almacene esos datos internamente), pero puede detener el proceso limpiamente sin lanzar una excepción (el uso de excepciones como un medio de control del flujo , en lugar de señalar un evento excepcional, a menudo está mal visto). sobre por programadores [3] ).
Abstracciones comúnmente asociadas
Las siguientes abstracciones no son estrictamente hablando necesarias para trabajar con iterados, pero lo hacen más conveniente.
Enumeradores
Un enumerador es una abstracción conveniente para introducir datos en un iteratee desde una fuente de datos arbitraria. Normalmente, el enumerador se ocupará de cualquier limpieza de recursos necesaria asociada con la fuente de datos. Debido a que el enumerador sabe exactamente cuándo el iteratee ha terminado de leer los datos, realizará la limpieza de recursos (como cerrar un archivo) exactamente en el momento adecuado, ni demasiado pronto ni demasiado tarde. Sin embargo, puede hacer esto sin necesidad de conocer la implementación del iteratee o de estar ubicado junto a él, por lo que los enumeradores y los iteratees constituyen un ejemplo de separación de preocupaciones .
Enumerados
Un Enumeratee es una abstracción conveniente para transformar la salida de un enumerador o iteratee, y alimentar esa salida a un iteratee. Por ejemplo, un "mapa" enumeratee sería mapear una función sobre cada trozo de entrada. [4]
Motivaciones
Los iterados se crearon debido a problemas con las soluciones puramente funcionales existentes al problema de hacer que la entrada / salida sea componible pero correcta. Lazy I / O en Haskell permitía que las funciones puras operaran en datos en el disco como si estuvieran en la memoria, sin hacer E / S explícitamente después de abrir el archivo, una especie de función de archivo mapeado en memoria , pero porque era imposible en general (debido al problema de detención ) para que el tiempo de ejecución sepa si el archivo u otro recurso aún se necesita, un número excesivo de archivos podría dejarse abierto innecesariamente, lo que provocaría el agotamiento del descriptor de archivo en el nivel del sistema operativo . La E / S tradicional de estilo C , por otro lado, era de un nivel demasiado bajo y requería que el desarrollador se preocupara por los detalles de bajo nivel, como la posición actual en el archivo, lo que dificultaba la componibilidad. Los iteradores y enumeradores combinan los beneficios de programación funcional de alto nivel de la E / S diferida, con la capacidad de controlar los recursos y los detalles de bajo nivel cuando sea necesario que ofrece la E / S de estilo C. [5]
Ejemplos de
Usos
Los iterados se utilizan en el marco de Play para enviar datos a las conexiones Comet y WebSocket de larga ejecución a los navegadores web .
Los iterados también se pueden usar para realizar un análisis incremental (es decir, un análisis que no lee todos los datos en la memoria a la vez), por ejemplo, de JSON . [6]
Sin embargo, es importante tener en cuenta que los iterados son una abstracción muy general y pueden usarse para tipos arbitrarios de procesamiento de información secuencial (o procesamiento mixto secuencial / de acceso aleatorio) y no necesitan involucrar ninguna E / S en absoluto. Esto facilita la reutilización de un iteratee para que trabaje en un conjunto de datos en memoria en lugar de los datos que fluyen desde la red.
Historia
En cierto sentido, un antecesor lejano de la noción de un enumerador que empuja los datos en una cadena de uno o más iterados fue el concepto de canalización en los sistemas operativos. Sin embargo, a diferencia de una canalización típica, los iterados no son procesos separados (y, por lo tanto, no tienen la sobrecarga de IPC ), ni siquiera subprocesos separados, aunque pueden realizar el trabajo de manera similar a una cadena de subprocesos de trabajo que se envían mensajes entre sí. Esto significa que los iterados son más ligeros que los procesos o subprocesos; a diferencia de las situaciones con procesos o subprocesos separados, no se necesitan pilas adicionales.
Los iteradores y enumeradores fueron inventados por Oleg Kiselyov para su uso en Haskell. [5] Más tarde, se introdujeron en Scalaz (en la versión 5.0; los enumerados estaban ausentes y se introdujeron en Scalaz 7) y en Play Framework 2.0.
Semántica formal
Los iterados se han modelado formalmente como mónadas libres , lo que permite validar las leyes de ecuaciones y emplearlas para optimizar programas que utilizan iterados. [5]
Alternativas
- Se pueden usar iteradores en lugar de iterados en Scala, pero son imperativos , por lo que no son una solución puramente funcional .
- En Haskell, se han desarrollado dos abstracciones alternativas conocidas como Conductos y Tuberías. (Estas tuberías no son tuberías a nivel de sistema operativo, por lo que, al igual que las iteraciones, no requieren el uso de llamadas al sistema ). Los conductos en particular están asociados con bibliotecas de primitivas y combinadores sustancialmente más ricas que los iterados; existen adaptadores de conductos para funcionalidades incrementales como analizar HTML, XML, análisis generalizado, realizar solicitudes HTTP y procesar las respuestas, lo que hace que los conductos sean más adecuados que los iterados para el desarrollo de software industrial en Haskell, listos para usar.
- También hay una abstracción de alto nivel llamada máquinas . En Scala hay un paquete llamado FS2: Functional Streams for Scala , cuya ascendencia se puede rastrear hasta las máquinas a través de varios puertos, renombramientos y refactores.
- En Haskell, existe el paquete safe-lazy-io . Proporciona una solución más simple a algunos de los mismos problemas, que esencialmente implica ser "lo suficientemente estricto" para extraer todos los datos que se requieren, o podrían ser necesarios, a través de una tubería que se encarga de limpiar los recursos al finalizar.
Referencias
- ^ "Manejo de flujos de datos de forma reactiva" . Documentación de Play Framework . Consultado el 29 de junio de 2013 .
- ^ "Resultados de búsqueda de Github: iteratee en FSharpx" .
- ^ "Teoría y práctica de Java: el debate de las excepciones" . IBM developerWorks . Consultado el 17 de mayo de 2014 .
- ^ "Enumerados" . Reproducir la documentación del marco . Consultado el 29 de junio de 2013 .
- ^ a b c Kiselyov, O. (2012). "Iteratees". Programación funcional y lógica . Apuntes de conferencias en Ciencias de la Computación. 7294 . págs. 166-181. doi : 10.1007 / 978-3-642-29822-6_15 . ISBN 978-3-642-29821-9.
- ^ James Roper (10 de diciembre de 2012). "Json.scala" . play-iteratees-extras . Consultado el 29 de junio de 2013 .
Otras lecturas
- John W. Lato (12 de mayo de 2010). "Iteratee: enseñar nuevos trucos a un viejo pliegue" . Número 16 de The Monad Reader . Consultado el 29 de junio de 2013 . Esto se relaciona con Haskell.
enlaces externos
- Tutoriales de Scala
- Jugar 2.0
- Entendiendo Play 2 iterados para humanos normales
- Iteratees para programadores imperativos
- Scalaz
- Tutorial de Scalaz: E / S basada en enumeración con iteraciones
- Jugar 2.0
- Tutoriales de Haskell
- Notas de la conferencia de Stanford
- Más información
- Página de iteradores y enumeradores de Oleg Kiselyov