Eu geralmente ouvi que o código de produção deve evitar o uso de E / S lenta. Minha pergunta é, por quê? É normal usar o Lazy I / O fora de apenas brincar? E o que torna as alternativas (por exemplo, enumeradores) melhores?
fonte
Eu geralmente ouvi que o código de produção deve evitar o uso de E / S lenta. Minha pergunta é, por quê? É normal usar o Lazy I / O fora de apenas brincar? E o que torna as alternativas (por exemplo, enumeradores) melhores?
O Lazy IO tem o problema de liberar qualquer recurso que você adquiriu seja um tanto imprevisível, pois depende de como seu programa consome os dados - seu "padrão de demanda". Uma vez que seu programa elimine a última referência ao recurso, o GC irá eventualmente executar e liberar esse recurso.
Streams lentos são um estilo muito conveniente de programar. É por isso que os shell pipes são tão divertidos e populares.
No entanto, se os recursos forem limitados (como em cenários de alto desempenho ou ambientes de produção que esperam escalar até os limites da máquina), contar com o GC para limpar pode ser uma garantia insuficiente.
Às vezes, você precisa liberar recursos avidamente, a fim de melhorar a escalabilidade.
Então, quais são as alternativas para IO preguiçoso que não significa desistir do processamento incremental (que por sua vez consumiria muitos recursos)? Bem, temos o foldl
processamento baseado, também conhecido como iteratees ou enumeradores, introduzido por Oleg Kiselyov no final dos anos 2000 e, desde então, popularizado por uma série de projetos baseados em rede.
Em vez de processar dados como fluxos lazy, ou em um lote enorme, em vez disso, abstraímos o processamento estrito baseado em chunk, com finalização garantida do recurso assim que o último chunk for lido. Essa é a essência da programação baseada em iteratee, que oferece ótimas restrições de recursos.
A desvantagem do IO baseado em iteratee é que ele tem um modelo de programação um tanto estranho (aproximadamente análogo à programação baseada em eventos, versus um bom controle baseado em thread). É definitivamente uma técnica avançada, em qualquer linguagem de programação. E para a grande maioria dos problemas de programação, IO preguiçoso é inteiramente satisfatório. No entanto, se você for abrir muitos arquivos, ou falar em muitos sockets, ou usar muitos recursos simultâneos, uma abordagem iteratária (ou enumerador) pode fazer sentido.
Dons forneceu uma resposta muito boa, mas deixou de fora o que é (para mim) uma das características mais atraentes dos iteratees: eles tornam mais fácil raciocinar sobre o gerenciamento do espaço porque os dados antigos devem ser retidos explicitamente. Considerar:
Este é um vazamento de espaço bem conhecido, porque a lista inteira
xs
deve ser retida na memória para calcularsum
elength
. É possível tornar um consumidor eficiente criando uma dobra:Mas é um tanto inconveniente ter que fazer isso para todos os processadores de fluxo. Existem algumas generalizações ( Conal Elliott - Beautiful Fold Zipping ), mas elas não parecem ter pegado. No entanto, os iteratees podem obter um nível semelhante de expressão.
Isso não é tão eficiente quanto uma dobra porque a lista ainda é iterada várias vezes; no entanto, é coletada em partes para que os dados antigos possam ser coletados de forma eficiente como lixo. Para quebrar essa propriedade, é necessário reter explicitamente toda a entrada, como com stream2list:
O estado dos iteratees como um modelo de programação é um trabalho em andamento, no entanto, é muito melhor do que há um ano. Nós estamos aprendendo o que combinators são úteis (por exemplo
zip
,breakE
,enumWith
) e que são menos assim, com o resultado que built-in iteratees e combinadores fornecer continuamente mais expressividade.Dito isso, Dons está correto ao dizer que eles são uma técnica avançada; Eu certamente não os usaria para todos os problemas de E / S.
fonte
Eu uso I / O lento no código de produção o tempo todo. É apenas um problema em certas circunstâncias, como Don mencionou. Mas para apenas ler alguns arquivos funciona bem.
fonte
Atualização: Recentemente, no haskell-cafe, Oleg Kiseljov mostrou que
unsafeInterleaveST
(que é usado para implementar IO preguiçoso dentro da mônada ST) é muito inseguro - quebra o raciocínio equacional. Ele mostra que permite construir debad_ctx :: ((Bool,Bool) -> Bool) -> Bool
tal forma queembora
==
seja comutativo.Outro problema com o IO lento: a operação real do IO pode ser adiada até que seja tarde demais, por exemplo, depois que o arquivo for fechado. Citando de Haskell Wiki - Problemas com IO preguiçoso :
Isso geralmente é inesperado e um erro fácil de cometer.
Consulte também: Três exemplos de problemas com E / S lenta .
fonte
hGetContents
ewithFile
é inútil porque o primeiro coloca o identificador em um estado "pseudo-fechado" e fará o fechamento para você (preguiçosamente), de modo que o código é exatamente equivalente aoreadFile
, ou mesmoopenFile
semhClose
. Isso é basicamente o preguiçoso I / O é . Se você não usarreadFile
,getContents
ouhGetContents
você não está usando preguiçoso I / O. Por exemplo,line <- withFile "test.txt" ReadMode hGetLine
funciona bem.hGetContents
fechar o arquivo para você, também é permitido fechá-lo "mais cedo" e ajuda a garantir que os recursos sejam liberados de maneira previsível.Outro problema com IO preguiçoso que não foi mencionado até agora é que ele tem um comportamento surpreendente. Em um programa Haskell normal, às vezes pode ser difícil prever quando cada parte do programa é avaliada, mas felizmente, devido à pureza, isso realmente não importa, a menos que você tenha problemas de desempenho. Quando o IO preguiçoso é introduzido, a ordem de avaliação do seu código realmente tem um efeito sobre seu significado, portanto, as alterações que você está acostumado a considerar inofensivas podem causar problemas genuínos.
Por exemplo, aqui está uma pergunta sobre o código que parece razoável, mas fica mais confusa pelo IO adiado: withFile vs. openFile
Esses problemas não são invariavelmente fatais, mas é outra coisa a se pensar, e uma dor de cabeça suficientemente forte que eu pessoalmente evito IO preguiçoso, a menos que haja um problema real em fazer todo o trabalho antecipadamente.
fonte