Quais são os benefícios da transparência referencial para um programador?

18

Na programação, quais são os benefícios da transparência referencial ?

A TR faz uma das principais diferenças entre paradigmas funcionais e imperativos e é frequentemente usada pelos defensores do paradigma funcional como uma clara vantagem sobre o imperativo; mas em todos os seus esforços, esses advogados nunca explicam por que é um benefício para mim como programador .

Claro, eles terão suas explicações acadêmicas sobre o quão "puro" e "elegante" é, mas como isso o torna melhor do que um código menos "puro"? Como isso me beneficia na minha programação do dia-a-dia?

Nota: Esta não é uma duplicata de O que é transparência referencial? O último aborda o tópico do que é RT, enquanto essa pergunta aborda seus benefícios (que podem não ser tão intuitivos).

Eyal Roth
fonte
Veja também: stackoverflow.com/questions/210835/…
meriton - em greve
3
A transparência referencial permite que você use o raciocínio equacional para: 1) provar propriedades do código e 2) escrever programas. Existem alguns livros sobre Haskell em que os autores devem começar a partir de algumas equações que você deseja que uma função preencha e, usando apenas o raciocínio equacional, você acaba obtendo uma implementação da referida função, que é, portanto, certamente correta. Agora o quanto isso pode ser aplicado no "dia-a-dia" programação provavelmente depende do contexto ...
Bakuriu
2
@err Você gosta de código mais fácil de refatorar, porque sabe se chamar uma função duas vezes é o mesmo que armazenar seu valor em uma variável e depois usar a referida variável duas vezes? Você diria que isso é um benefício para a sua programação do dia a dia?
18716 Andres F.
O benefício é que você não precisa perder tempo pensando em não-transparência referencial. Como os benefícios das variáveis, você não precisa perder tempo pensando na alocação de registros.
user253751

Respostas:

37

O benefício é que funções puras facilitam o raciocínio do seu código. Ou, em outras palavras, os efeitos colaterais aumentam a complexidade do seu código.

Veja um exemplo de computeProductPricemétodo.

Um método puro solicitaria uma quantidade de produto, uma moeda etc. Você sabe que sempre que o método é chamado com os mesmos argumentos, sempre produz o mesmo resultado.

  • Você pode até armazená-lo em cache e usar a versão em cache.
  • Você pode torná-lo preguiçoso e adiar sua chamada para quando realmente precisar, sabendo que o valor não mudará enquanto isso.
  • Você pode chamar o método várias vezes, sabendo que ele não terá efeitos colaterais.
  • Você pode raciocinar sobre o próprio método isoladamente do mundo, sabendo que tudo o que precisa são os argumentos.

Um método não puro será mais complexo para usar e depurar. Uma vez que depende do estado das variáveis ​​que não sejam os argumentos e possivelmente alterá-las, significa que poderia produzir resultados diferentes quando chamados várias vezes, ou não ter o mesmo comportamento quando não chamado ou chamado muito cedo ou muito tarde.

Exemplo

Imagine que existe um método na estrutura que analisa um número:

decimal math.parse(string t)

Não possui transparência referencial, porque depende de:

  • A variável de ambiente que especifica o sistema de numeração, que é a Base 10 ou outra coisa.

  • A variável na mathbiblioteca que especifica a precisão dos números a serem analisados. Portanto, com o valor de 1, a análise da string "12.3456"dará 12.3.

  • A cultura, que define a formatação esperada. Por exemplo, com fr-FR, a análise "12.345"dará 12345, porque o caractere de separação deve ser ,, não.

Imagine como seria fácil ou difícil trabalhar com esse método. Com a mesma entrada, é possível obter resultados radicalmente diferentes, dependendo do momento em que você chama o método, porque algo em algum lugar mudou a variável de ambiente ou mudou a cultura ou definiu uma precisão diferente. O caráter não determinístico do método levaria a mais erros e a mais pesadelos de depuração. Ligar math.parse("12345")e obter 5349como resposta, já que algum código paralelo estava analisando números octais não é bom.

Como consertar esse método obviamente quebrado? Introduzindo transparência referencial. Em outras palavras, se livrando do estado global e movendo tudo para os parâmetros do método:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

Agora que o método é puro, você sabe que não importa quando você chama o método, ele sempre produzirá o mesmo resultado para os mesmos argumentos.

Arseni Mourzenko
fonte
4
Apenas um adendo: a transparência referencial se aplica a todas as expressões em um idioma, não apenas às funções.
gardenhead
3
Observe que existem limitações sobre como você pode ser transparente. Tornar packet = socket.recv()referencialmente transparente derrota o ponto da função.
Mark
1
Deve ser cultura = culturas.invariante. A menos que você queira criar acidentalmente um software que só funcione corretamente nos EUA.
user253751
@immibis: hm, boa pergunta. Quais seriam as regras de análise invariant? Ou são os mesmos de en_us, nesse caso, por que se preocupar, ou correspondem a algum outro país; nesse caso, qual e por que esse em vez de en_us, ou eles têm regras específicas que não correspondem a nenhum país de qualquer maneira , o que seria inútil. Não existe realmente uma "resposta verdadeira" entre 12,345.67e 12 345,67: quaisquer "regras padrão" funcionarão para alguns países e não funcionarão para outros.
Arseni Mourzenko
3
@ArseniMourzenko Geralmente é um "menor denominador comum" e semelhante à sintaxe usada por muitas linguagens de programação (que também é invariante na cultura). 12345analisa como 12345 12 345ou 12,345ou 12.345é um erro. 12.345analisado como um número de ponto flutuante invariável sempre gera 12.345, de acordo com a convenção da linguagem de programação de usar. como o separador decimal. As strings são classificadas por seus pontos de código Unicode e diferenciam maiúsculas de minúsculas. E assim por diante.
user253751
11

Você costuma adicionar um ponto de interrupção a um ponto do seu código e executar o aplicativo no depurador para descobrir o que está acontecendo? Se o fizer, é porque você não está usando transparência referencial (RT) em seus projetos. E, portanto, é necessário executar o código para descobrir o que ele faz.

O ponto principal para a RT é que o código é altamente determinístico, ou seja, você pode ler o código e descobrir o que ele faz, sempre, para o mesmo conjunto de entradas. Depois de começar a adicionar variáveis ​​mutantes, algumas das quais têm escopo além de uma única função, você não pode simplesmente ler o código. Esse código deve ser executado, na sua cabeça ou no depurador, para descobrir como ele realmente funciona.

Quanto mais simples o código é a leitura e o raciocínio, mais simples é a manutenção e a identificação de bugs, economizando tempo e dinheiro para você e seu empregador.

David Arno
fonte
10
"Uma vez que você começa a adicionar variáveis ​​mutantes, algumas das quais têm além de uma única função, você não pode apenas ler o código, precisa executá-lo, seja na sua cabeça ou no depurador, para descobrir como ele realmente funciona. ": Bom ponto. Em outras palavras, transparência referencial não significa apenas que um trecho de código sempre produzirá o mesmo resultado para as mesmas entradas, mas também que o resultado produzido é o único efeito desse trecho de código, que não há outro lado oculto efeito como alterar alguma variável que foi definida distante em outro módulo.
Giorgio
Essa é uma boa opinião. Eu tenho um pouco de problema com o argumento mais simples de ler / raciocinar , pois mais simples de ler ou raciocinar é um atributo um tanto vago e subjetivo do código.
Eyal Roth
Depois de começar a adicionar variáveis ​​mutantes, algumas das quais têm escopo além de uma única função, mas por que a operação de atribuição é desencorajada, mesmo quando o escopo da variável é local para a função?
Rhulaga_dev 28/11
9

As pessoas usam o termo "mais fácil de raciocinar", mas nunca explicam o que isso significa. Considere o seguinte exemplo:

result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)

São result1e result2iguais ou diferentes? Sem transparência referencial, você não tem ideia. Você precisa realmente ler o corpo de foopara ter certeza, e possivelmente o corpo de qualquer função foo, e assim por diante.

As pessoas não percebem esse fardo porque estão acostumados a ele, mas se você trabalhar em um ambiente puramente funcional por um mês ou dois, depois voltar, sentirá isso, e é um grande negócio .

Existem muitos mecanismos de defesa que as pessoas fazem para solucionar a falta de transparência referencial. Para o meu pequeno exemplo, talvez eu queira ficar result1na memória, porque não saberia se isso mudaria. Então eu tenho código com dois estados: antes result1foi armazenado e depois. Com transparência referencial, posso apenas recalcular com facilidade, desde que o recálculo não consuma tempo.

Karl Bielefeldt
fonte
1
Você mencionou que a transparência referencial permite raciocinar sobre o resultado das chamadas para foo () e saber se result1e result2são os mesmos. Outro aspecto importante é que, se foo("bar", 12)for referencialmente transparente, você não precisa se perguntar se essa chamada produziu efeitos em outro lugar (defina algumas variáveis? Excluiu um arquivo? Qualquer que seja).
Giorgio
A única "integridade referencial" com a qual estou familiarizado envolve bancos de dados relacionais.
Mark
1
@ Mark É um erro de digitação. Karl quis dizer transparência referencial, como é óbvio no restante de sua resposta.
18716 Andres F.
6

Eu diria: a transparência referencial não é boa apenas para programação funcional, mas para todos que trabalham com funções porque seguem o princípio de menor espanto.

Você tem uma função e pode raciocinar melhor sobre o que faz, porque não há fatores externos que precisam ser levados em consideração; para uma determinada entrada, a saída será sempre a mesma. Mesmo na minha linguagem imperativa, tento seguir esse paradigma o máximo possível, a próxima coisa que se segue automaticamente é: funções pequenas e fáceis de entender, em vez das funções terríveis de mais de 1000 linhas nas quais eu às vezes corro.

Essas grandes funções fazem mágica e eu tenho medo de tocá-las porque podem quebrar de maneiras espetaculares.

Portanto, funções puras não são apenas para programação funcional, mas para todos os programas.

Pieter B
fonte