Iteratee - Iteratee
Na programação funcional , um iteratário é uma abstração combinável para processar de forma incremental pedaços de dados de entrada apresentados sequencialmente de uma forma puramente funcional . Com iteratees, é possível transformar lentamente como um recurso emitirá dados, por exemplo, convertendo cada pedaço da entrada em maiúsculas à medida que são recuperados ou limitando os dados a apenas os cinco primeiros pedaços sem carregar todos os dados de entrada em memória. Os iteratees também são responsáveis por abrir e fechar recursos, fornecendo gerenciamento de recursos previsível.
Em cada etapa, um iteratário é apresentado com um dos três tipos possíveis de valores: o próximo bloco de dados, um valor para indicar que nenhum dado está disponível ou um valor para indicar que o processo de iteração foi concluído. Ele pode retornar um dos três tipos possíveis de valores, para indicar ao chamador o que deve ser feito a seguir: um que significa "parar" (e contém o valor de retorno final), um que significa "continuar" (e especifica como continuar) , e um que significa "sinalizar um erro". Os últimos tipos de valores em vigor representam os "estados" possíveis de um iteratê. Um iteratee normalmente iniciaria no estado "continuar".
Iteratees são usados em Haskell e Scala (no Play Framework e em Scalaz ) e também estão disponíveis para F # . Existem várias implementações ligeiramente diferentes de iteratees. Por exemplo, na estrutura Play, eles envolvem Futures para que o processamento assíncrono possa ser executado.
Como os iteratees são chamados por outro código que os alimenta com dados, eles são um exemplo de inversão de controle . No entanto, ao contrário de muitos outros exemplos de inversão de controle, como análise SAX XML, o iteratário retém uma quantidade limitada de controle sobre o processo. Ele não pode reverter e olhar para os dados anteriores (a menos que armazene esses dados internamente), mas pode interromper o processo de forma limpa, sem lançar uma exceção (usar exceções como meio de fluxo de controle , em vez de sinalizar um evento excepcional, é muitas vezes franzido por programadores).
Abstrações comumente associadas
As abstrações a seguir não são estritamente necessárias para trabalhar com iteratees, mas o tornam mais conveniente.
Enumeradores
Um Enumerador é uma abstração conveniente para alimentar dados em um iteratee de uma fonte de dados arbitrária. Normalmente, o enumerador cuidará de qualquer limpeza de recursos necessária associada à fonte de dados. Como o enumerador sabe exatamente quando o iteratário terminou de ler os dados, ele fará a limpeza do recurso (como fechar um arquivo) exatamente no momento certo - nem muito cedo nem muito tarde. No entanto, ele pode fazer isso sem precisar saber sobre, ou ser co-localizado para, a implementação do iteratee - portanto, enumeradores e iteratees formam um exemplo de separação de interesses .
Enumeratees
Um Enumeratee é uma abstração conveniente para transformar a saída de um enumerador ou iteratário e alimentar essa saída para um iteratário. Por exemplo, um "mapa" enumeratee iria mapear uma função sobre cada pedaço de entrada.
Motivações
Iteratees foram criados devido a problemas com soluções puramente funcionais existentes para o problema de tornar a entrada / saída combinável, mas correta. I / O lento em Haskell permitia que funções puras operassem em dados no disco como se estivessem na memória, sem fazer I / O explicitamente depois de abrir o arquivo - um tipo de recurso de arquivo mapeado em memória - mas porque era impossível em geral (devido ao problema de Halting ) para o tempo de execução saber se o arquivo ou outro recurso ainda era necessário, um número excessivo de arquivos poderia ser deixado aberto desnecessariamente, resultando no esgotamento do descritor de arquivo no nível do sistema operacional . A E / S tradicional no estilo C , por outro lado, era de nível muito baixo e exigia que o desenvolvedor se preocupasse com detalhes de baixo nível, como a posição atual no arquivo, o que dificultava a composição. Iteratees e enumeradores combinam os benefícios de programação funcional de alto nível do I / O lento, com a capacidade de controlar recursos e detalhes de baixo nível, quando necessário, proporcionados pelo I / O estilo C.
Exemplos
Usos
Os iteratees são usados na estrutura Play para enviar dados para conexões Comet e WebSocket de longa execução para navegadores da web .
Iteratees também podem ser usados para realizar análise incremental (ou seja, análise que não lê todos os dados na memória de uma vez), por exemplo de JSON .
É importante notar, entretanto, que os iteratees são uma abstração muito geral e podem ser usados para tipos arbitrários de processamento de informações sequenciais (ou processamento misto de acesso sequencial / aleatório) - e não precisam envolver nenhuma E / S. Isso torna mais fácil redirecionar um iteratee para trabalhar em um conjunto de dados na memória, em vez de dados fluindo da rede.
História
Em certo sentido, um antecessor distante da noção de um enumerador empurrando dados em uma cadeia de um ou mais iteratees, foi o conceito de pipeline em sistemas operacionais. No entanto, ao contrário de um pipeline típico, os iteratees não são processos separados (e, portanto, não têm a sobrecarga do IPC ) - ou mesmo threads separados, embora possam realizar o trabalho de maneira semelhante a uma cadeia de threads de trabalho que enviam mensagens entre si. Isso significa que os iteratees são mais leves do que processos ou threads - ao contrário das situações com processos ou threads separados, nenhuma pilha extra é necessária.
Iteratees e enumeradores foram inventados por Oleg Kiselyov para uso em Haskell. Posteriormente, eles foram introduzidos no Scalaz (na versão 5.0; os enumeratees estavam ausentes e foram introduzidos no Scalaz 7) e no Play Framework 2.0.
Semântica formal
Os iteratees foram formalmente modelados como mônadas livres , permitindo que as leis equacionais sejam validadas e empregadas para otimizar programas usando iteratees.
Alternativas
- Iteradores podem ser usados em vez de iteratees no Scala, mas eles são imperativos , portanto, não são uma solução puramente funcional .
- Em Haskell, duas abstrações alternativas conhecidas como Conduits e Pipes foram desenvolvidas. (Esses Pipes não são canais de nível de sistema operacional, portanto, como iteratees, eles não requerem o uso de chamadas de sistema ). Os conduítes, em particular, estão associados a bibliotecas substancialmente mais ricas de primitivas e combinadores do que iteratês; adaptadores de conduíte para funcionalidades incrementais, como análise de HTML, XML, análise generalizada, fazer solicitações de HTTP e processar as respostas, existem, tornando os conduítes mais adequados do que iteratees para o desenvolvimento de software industrial em Haskell, prontos para uso.
- Há também uma abstração de alto nível denominada máquinas . No Scala, há um pacote chamado FS2: Functional Streams for Scala , cuja ancestralidade pode ser rastreada até as máquinas por meio de várias portas, renomeações e refatores.
- Em Haskell, o pacote safe-lazy-io existe. Ele fornece uma solução mais simples para alguns dos mesmos problemas, que essencialmente envolve ser "estrito o suficiente" para extrair todos os dados que são necessários, ou podem ser necessários, por meio de um pipeline que cuida da limpeza dos recursos na conclusão.
Referências
Leitura adicional
- John W. Lato (12 de maio de 2010). "Iteratee: ensinando novos truques a uma dobra antiga" . Edição nº 16 de The Monad Reader . Retirado em 29 de junho de 2013 . Isso se relaciona com Haskell.
links externos
- Tutoriais Scala
- Tutoriais de Haskell
- Outras informações