Acabei de aprender como funciona a avaliação lenta e me perguntei: por que a avaliação lenta não é aplicada em todos os softwares atualmente produzidos? Por que ainda está usando avaliação ansiosa?
Acabei de aprender como funciona a avaliação lenta e me perguntei: por que a avaliação lenta não é aplicada em todos os softwares atualmente produzidos? Por que ainda está usando avaliação ansiosa?
Respostas:
A avaliação preguiçosa exige despesas gerais de contabilidade - você precisa saber se já foi avaliado e essas coisas. A avaliação ansiosa é sempre avaliada, para que você não precise saber. Isto é especialmente verdade em contextos concorrentes.
Em segundo lugar, é trivial converter a avaliação ansiosa em avaliação lenta, empacotando-a em um objeto de função a ser chamado posteriormente, se você desejar.
Em terceiro lugar, a avaliação preguiçosa implica perda de controle. E se eu avaliasse preguiçosamente a leitura de um arquivo de um disco? Ou conseguir tempo? Isso não é aceitável.
A avaliação ansiosa pode ser mais eficiente e controlável e é trivialmente convertida em avaliação lenta. Por que você quer uma avaliação preguiçosa?
readFile
é exatamente o que eu preciso. Além disso, a conversão da avaliação preguiçosa para a ansiosa é igualmente trivial.
head [1 ..]
você fornece em uma linguagem pura avaliada com avidez, porque em Haskell ele fornece 1
?
Principalmente porque código e estado preguiçosos podem se misturar mal e causar alguns erros difíceis de encontrar. Se o estado de um objeto dependente mudar, o valor do seu objeto lento pode estar errado quando avaliado. É muito melhor que o programador codifique explicitamente o objeto como preguiçoso quando souber que a situação é apropriada.
Em uma nota lateral, Haskell usa a avaliação Lazy para tudo. Isso é possível porque é uma linguagem funcional e não usa estado (exceto em algumas circunstâncias excepcionais em que elas estão claramente marcadas)
set!
de um intérprete preguiçoso do Scheme. > :(
A avaliação preguiçosa nem sempre é melhor.
Os benefícios de desempenho da avaliação lenta podem ser ótimos, mas não é difícil evitar a maioria das avaliações desnecessárias em ambientes ansiosos - certamente a preguiça a torna fácil e completa, mas raramente a avaliação desnecessária no código é um grande problema.
O lado bom da avaliação preguiçosa é quando ela permite escrever um código mais claro; obter o 10º primo filtrando uma lista de números naturais infinitos e pegar o 10º elemento dessa lista é uma das formas mais concisas e claras de proceder: (pseudocódigo)
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
Eu acredito que seria bastante difícil expressar as coisas de forma tão concisa sem preguiça.
Mas a preguiça não é a resposta para tudo. Para iniciantes, a preguiça não pode ser aplicada de forma transparente na presença de estado, e acredito que a condição de estado não pode ser detectada automaticamente (a menos que você esteja trabalhando, digamos, Haskell, quando o estado é bastante explícito). Portanto, na maioria dos idiomas, a preguiça precisa ser feita manualmente, o que torna as coisas menos claras e, portanto, remove um dos grandes benefícios da avaliação preguiçosa.
Além disso, a preguiça tem desvantagens de desempenho, pois incorre em uma sobrecarga significativa de manter expressões não avaliadas por perto; eles usam o armazenamento e são mais lentos para trabalhar do que os valores simples. Não é incomum descobrir que você precisa ter um código ansioso, porque a versão lenta é lenta - e às vezes é difícil pensar em desempenho.
Como costuma acontecer, não existe uma melhor estratégia absoluta. O Lazy é ótimo se você pode escrever um código melhor aproveitando infinitas estruturas de dados ou outras estratégias que ele permite que você use, mas o ansioso pode ser mais fácil de otimizar.
Aqui está uma pequena comparação dos prós e contras da avaliação ansiosa e preguiçosa:
Avaliação ansiosa:
Potencial sobrecarga de avaliar coisas desnecessariamente.
Avaliação rápida e sem obstáculos.
Avaliação preguiçosa:
Nenhuma avaliação desnecessária.
Sobrecarga da contabilidade a cada uso de um valor.
Portanto, se você tem muitas expressões que nunca precisam ser avaliadas, a preguiça é melhor; No entanto, se você nunca tiver uma expressão que não precise ser avaliada, a preguiça é pura sobrecarga.
Agora, vamos dar uma olhada no software do mundo real: Quantas das funções que você escreve não requerem avaliação de todos os seus argumentos? Especialmente com as funções curtas modernas que apenas fazem uma coisa, a porcentagem de funções se enquadra nessa categoria é muito baixa. Assim, uma avaliação preguiçosa apenas introduziria a sobrecarga da contabilidade na maioria das vezes, sem a chance de realmente salvar alguma coisa.
Consequentemente, a avaliação preguiçosa simplesmente não compensa, em média, a avaliação ansiosa é a melhor opção para o código moderno.
Como observou o @DeadMG, a avaliação preguiçosa exige despesas gerais de contabilidade. Isso pode ser caro em relação à avaliação ansiosa. Considere esta declaração:
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
Isso levará um pouco de cálculo para calcular. Se eu uso uma avaliação lenta, preciso verificar se ela foi avaliada toda vez que a uso. Se isso estiver dentro de um circuito fechado muito usado, a sobrecarga aumentará significativamente, mas não haverá benefícios.
Com uma avaliação ágil e um compilador decente, a fórmula é calculada em tempo de compilação. A maioria dos otimizadores retirará a atribuição de quaisquer loops em que ocorra, se apropriado.
A avaliação preguiçosa é mais adequada para carregar dados que serão acessados com pouca frequência e possuem uma alta sobrecarga para recuperar. Portanto, é mais apropriado desviar casos do que a funcionalidade principal.
Em geral, é uma boa prática avaliar as coisas que são freqüentemente acessadas o mais cedo possível. A avaliação preguiçosa não funciona com essa prática. Se você sempre acessará algo, tudo o que a avaliação preguiçosa fará é adicionar sobrecarga. O custo / benefício do uso da avaliação lenta diminui à medida que o item que está sendo acessado se torna menos provável.
Sempre usar a avaliação lenta também implica em otimização antecipada. Essa é uma prática ruim que geralmente resulta em código muito mais complexo e caro que, de outra forma, poderia ser o caso. Infelizmente, a otimização prematura geralmente resulta em código com desempenho mais lento que o mais simples. Até que você possa medir o efeito da otimização, é uma má idéia otimizar seu código.
Evitar a otimização prematura não entra em conflito com as boas práticas de codificação. Se não foram aplicadas boas práticas, as otimizações iniciais podem consistir na aplicação de boas práticas de codificação, como mover cálculos para fora dos loops.
Se precisarmos avaliar completamente uma expressão para determinar seu valor, a avaliação lenta pode ser uma desvantagem. Digamos que temos uma longa lista de valores booleanos e queremos descobrir se todos eles são verdadeiros:
[True, True, True, ... False]
Para fazer isso, precisamos examinar todos os elementos da lista, não importa o quê, para que não haja possibilidade de interromper a avaliação preguiçosamente. Podemos usar uma dobra para determinar se todos os valores booleanos da lista são verdadeiros. Se usarmos uma dobra à direita, que usa avaliação lenta, não obtemos nenhum dos benefícios da avaliação lenta porque precisamos examinar todos os elementos da lista:
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
Uma dobra à direita será muito mais lenta nesse caso do que uma dobra estrita à esquerda, que não usa avaliação lenta:
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
O motivo é que uma dobra estrita à esquerda usa recursão de cauda, o que significa que acumula o valor de retorno e não acumula e armazena na memória uma grande cadeia de operações. Isso é muito mais rápido que a dobra preguiçosa à direita, porque ambas as funções precisam olhar para toda a lista e a dobra à direita não pode usar a recursão da cauda. Portanto, o ponto é que você deve usar o que for melhor para a tarefa em questão.