Ter variáveis locais mutáveis em uma função que é usada apenas internamente (por exemplo, a função não tem efeitos colaterais, pelo menos não intencionalmente) ainda é considerado "não funcional"?
por exemplo, na verificação do estilo do curso "Programação funcional com Scala" considera qualquer var
uso ruim
Minha pergunta, se a função não tiver efeitos colaterais, a escrita de código de estilo imperativo ainda é desencorajada?
por exemplo, em vez de usar recursão de cauda com o padrão de acumulador, o que há de errado em criar um loop for local e criar um mutável localListBuffer
e adicioná-lo, desde que a entrada não seja alterada?
Se a resposta for "sim, eles são sempre desencorajados, mesmo que não haja efeitos colaterais", qual é o motivo?
fonte
var
é sempre não funcional. O Scala possui valores preguiçosos e otimização da recursão da cauda, o que permite evitar completamente os vars.Respostas:
A única coisa que é inequivocamente uma prática ruim aqui é alegar que algo é uma função pura quando não é.
Se variáveis mutáveis são usadas de maneira verdadeira e completamente independente, a função é externamente pura e todos ficam felizes. Haskell, de fato, suporta isso explicitamente , com o sistema de tipos até garantindo que referências mutáveis não possam ser usadas fora da função que as cria.
Dito isto, acho que falar sobre "efeitos colaterais" não é a melhor maneira de encará-lo (e é por isso que disse "puro" acima). Qualquer coisa que crie uma dependência entre a função e o estado externo torna as coisas mais difíceis de raciocinar, e isso inclui coisas como saber a hora atual ou usar o estado mutável oculto de uma maneira que não seja segura para threads.
fonte
O problema não é a mutabilidade em si, é uma falta de transparência referencial.
Uma coisa referencialmente transparente e uma referência a ela sempre devem ser iguais; portanto, uma função referencialmente transparente sempre retornará os mesmos resultados para um determinado conjunto de entradas e uma "variável" referencialmente transparente é realmente um valor e não uma variável, pois não pode mudar. Você pode criar uma função referencialmente transparente que possui uma variável mutável dentro; isso não é um problema. Pode ser mais difícil garantir que a função seja referencialmente transparente, dependendo do que você estiver fazendo.
Há um exemplo em que posso pensar em que a mutabilidade deve ser usada para fazer algo que é muito funcional: memorização. Memoização é o armazenamento em cache de valores de uma função, para que eles não precisem ser recalculados; é referencialmente transparente, mas usa mutação.
Mas, em geral, transparência referencial e imutabilidade andam juntas, exceto uma variável local mutável em uma função e memoização referencialmente transparente, não tenho certeza de que haja outros exemplos em que esse não seja o caso.
fonte
Não é realmente bom resumir isso em "boas práticas" versus "más práticas". O Scala suporta valores mutáveis porque eles resolvem certos problemas muito melhor do que valores imutáveis, nomeadamente aqueles que são de natureza iterativa.
Para perspectiva, tenho
CanBuildFrom
quase certeza de que, através de quase todas as estruturas imutáveis fornecidas pelo scala, ocorre algum tipo de mutação internamente. O ponto é que o que eles expõem é imutável. Manter o maior número possível de valores imutáveis ajuda a tornar o programa mais fácil de raciocinar e menos propenso a erros .Isso não significa que você necessariamente precise evitar estruturas e valores mutáveis internamente quando tiver um problema mais adequado à mutabilidade.
Com isso em mente, muitos problemas que normalmente requerem variáveis mutáveis (como loop) podem ser resolvidos melhor com muitas das funções de ordem superior fornecidas por linguagens como Scala (map / filter / fold). Esteja ciente disso.
fonte
map
,filter
,foldLeft
EforEach
fazer o truque na maioria das vezes, mas não quando o fazem, ser capaz de sentir que eu sou "OK" para reverter para código imperativo força bruta é bom. (contanto que não existem efeitos secundários, é claro)Além de possíveis problemas com a segurança do encadeamento, você também perde muito tipo de segurança. Loops imperativos têm um tipo de retorno
Unit
e podem receber praticamente qualquer expressão para entradas. Funções de ordem superior e até recursão têm tipos e semânticas muito mais precisas.Você também tem muito mais opções para processamento funcional de contêiner do que com loops imperativos. Com imperativo, você tem basicamente
for
,while
e pequenas variações sobre aqueles dois comodo...while
eforeach
.Em funcional, você tem agregado, contagem, filtro, localização, flatMap, dobra, groupBy, lastIndexWhere, mapa, maxBy, minBy, partição, varredura, varredura, sortBy, sortWith, span e takeWhile, apenas para citar alguns exemplos mais comuns do Scala. biblioteca padrão. Quando você se acostuma a ter esses disponíveis, os
for
loops imperativos parecem muito básicos em comparação.A única razão real para usar a mutabilidade local é muito ocasionalmente para o desempenho.
fonte
Eu diria que é principalmente ok. Além disso, gerar estruturas dessa maneira pode ser uma boa maneira de melhorar o desempenho em alguns casos. Clojure resolveu esse problema fornecendo estruturas de dados temporárias .
A idéia básica é permitir mutações locais em um escopo limitado e congelar a estrutura antes de devolvê-la. Dessa forma, seu usuário ainda pode raciocinar sobre seu código como se fosse puro, mas você pode executar transformações no local quando necessário.
Como o link diz:
fonte
Não ter nenhuma variável local mutável tem uma vantagem - torna a função mais amigável em relação aos threads.
Fui queimado por uma variável local (não no meu código, nem tinha a fonte), causando uma corrupção de dados de baixa probabilidade. A segurança do encadeamento não foi mencionada de uma forma ou de outra, não houve estado que persistisse nas chamadas e não houve efeitos colaterais. Não me ocorreu que ele pode não ser seguro para threads, perseguir uma corrupção de dados aleatória de 1 em 100.000 é uma dor real.
fonte