Por que os efeitos colaterais são considerados ruins na programação funcional?


69

Eu sinto que os efeitos colaterais são um fenômeno natural. Mas é algo como tabu em linguagens funcionais. Quais são as razões?

Minha pergunta é específica para o estilo de programação funcional. Nem todas as linguagens / paradigmas de programação.


6
Um programa sem efeitos colaterais é inútil; portanto, os efeitos colaterais não são maus nem tabus. Mas o FP incentiva a delimitação do código com efeitos colaterais, portanto, a maior parte possível do código são funções livres de efeitos colaterais. Isso é incentivado porque as funções e subsistemas livres de efeitos colaterais são mais fáceis de entender, mais fáceis de analisar, mais fáceis de testar e mais fáceis de otimizar.
JacquesB

@JacquesB Seria uma boa resposta explicar por que eles são mais fáceis de entender, mais fáceis de analisar, mais fáceis de testar e mais fáceis de otimizar.
ceving 13/12/16

Respostas:


72

Escrever suas funções / métodos sem efeitos colaterais - para que sejam funções puras - facilita o raciocínio sobre a correção do seu programa.

Também facilita a composição dessas funções para criar um novo comportamento.

Também possibilita certas otimizações, nas quais o compilador pode, por exemplo, memorizar os resultados de funções ou usar a eliminação de subexpressão comum.

Edit: a pedido de Benjol: como muitos de seus estados estão armazenados na pilha (fluxo de dados, não fluxo de controle, como Jonas o chamou aqui ), você pode paralelizar ou reordenar a execução das partes de sua computação que são independentes de entre si. Você pode encontrar facilmente essas partes independentes porque uma parte não fornece entradas para a outra.

Em ambientes com depuradores que permitem reverter a pilha e retomar a computação (como o Smalltalk), ter funções puras significa que você pode ver facilmente como um valor muda, porque os estados anteriores estão disponíveis para inspeção. Em um cálculo com muita mutação, a menos que você inclua explicitamente ações de fazer / desfazer em sua estrutura ou algoritmo, não poderá ver o histórico da computação. (Isso está relacionado ao primeiro parágrafo: escrever funções puras facilita a inspeção da correção do seu programa.)


4
Talvez considere adicionar algo sobre concorrência na sua resposta?
Benjol

5
As funções livres de efeitos colaterais são mais fáceis de testar e reutilizar.
LennyProgrammers

@ Lenny222: reutilizar era o que eu estava sugerindo falando sobre composição de funções.
Frank Shearar

@ Frank: Ah, ok, navegação muito superficial. :)
LennyProgrammers

@ Lenny222: Tudo bem; provavelmente é uma coisa boa explicá-la.
Frank Shearar

23

De um artigo sobre programação funcional :

Na prática, os aplicativos precisam ter alguns efeitos colaterais. Simon Peyton-Jones, um dos principais colaboradores da linguagem de programação funcional Haskell, disse o seguinte: "No final, qualquer programa deve manipular o estado. Um programa que não tem efeitos colaterais é uma espécie de caixa preta. Tudo o que você pode dizer é que a caixa fica mais quente ". ( http://oscon.blip.tv/file/324976 ) A chave é limitar os efeitos colaterais, identificá-los claramente e evitar dispersá-los por todo o código.



23

Você entendeu errado, a programação funcional promove efeitos colaterais limitantes para facilitar a compreensão e otimização dos programas. Até o Haskell permite gravar em arquivos.

Essencialmente, o que estou dizendo é que os programadores funcionais não acham os efeitos colaterais ruins, simplesmente acham que limitar o uso de efeitos colaterais é bom. Eu sei que pode parecer uma distinção tão simples, mas faz toda a diferença.


É por isso que eles são "algo como tabu" - as FPLs incentivam você a limitar os efeitos colaterais.
Frank Shearar 28/10/10

+1 para a abordagem. efeitos colaterais ainda existem. na verdade, eles são limitados
Belun

Para esclarecimento, eu não disse 'por que efeito colateral não é permitido na programação funcional' ou 'por que efeito colateral não é necessário'. Eu sei que é permitido em linguagens funcionais e às vezes é uma obrigação. Mas é muito desencorajado em programação funcional. Por quê? Essa foi a minha pergunta.
Gulshan #

@Gulshan - porque os efeitos colaterais tornam os programas mais difíceis de entender e otimizar.
ChaosPandion

no caso do haskell, o ponto principal não é "limitar os efeitos colaterais". efeitos colaterais são impossíveis de expressar em LANGUAGE. o que funções como readFileestão fazendo é definir uma sequência de ações. essa sequência é funcionalmente pura e é como uma árvore abstrata descrevendo o que fazer. os efeitos colaterais sujos reais são executados pelo tempo de execução.
Sara

13

Algumas notas:

  • Funções sem efeitos colaterais podem ser executadas de maneira trivial em paralelo, enquanto funções com efeitos colaterais normalmente requerem algum tipo de sincronização.

  • Funções sem efeitos colaterais permitem uma otimização mais agressiva (por exemplo, usando um cache de resultados de forma transparente), porque, enquanto obtemos o resultado certo, nem importa se a função foi realmente executada ou não


Ponto muito interessante: nem importa se a função foi realmente executada ou não . Seria interessante terminar com um compilador que pode se livrar de chamadas subseqüentes para funções livres de efeitos colaterais, com parâmetros equivalentes.
quer

11
@NoelWidmer Algo assim já existe. O PL / SQL da Oracle oferece uma deterministiccláusula para funções sem efeitos colaterais, para que elas não sejam executadas com mais frequência do que o necessário.
user281377

Uau! No entanto, acho que as linguagens devem ser semanticamente expressivas, para que o compilador possa descobrir por si só, sem precisar especificar um sinalizador explícito (não tenho certeza do que é uma cláusula). Uma solução poderia ser especificar parâmetros para serem mutáveis ​​/ imutáveis, em geral. Isso exigiria um sistema de tipo forte no qual as suposições sobre efeitos colaterais podem ser feitas pelo compilador. E o recurso precisaria ser desativado, se desejado. Desativar em vez de ativar. Isso é apenas a minha opinião, com base no conhecimento limitado que eu tenho desde que eu li a sua resposta :)
Noel Widmer

A deterministiccláusula é apenas uma palavra-chave que informa ao compilador que esta é uma função determinística, comparável à forma como a finalpalavra - chave em Java informa ao compilador que a variável não pode ser alterada.
user281377

11

Agora trabalho principalmente em código funcional e, dessa perspectiva, parece óbvio. Os efeitos colaterais criam um enorme fardo mental para os programadores que tentam ler e entender o código. Você não percebe esse fardo até ficar livre dele por um tempo e, de repente, precisa ler o código com efeitos colaterais novamente.

Considere este exemplo simples:

val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.

// Code you are troubleshooting
// What's the expected value of foo here?

Em uma linguagem funcional, eu sei que fooainda é 42. Nem preciso olhar para o código intermediário, muito menos entendê-lo, ou observar as implementações das funções que ele chama.

Todo esse material sobre concorrência, paralelização e otimização é bom, mas é isso que os cientistas da computação colocam no folheto. Não preciso me perguntar quem está mudando sua variável e quando é o que eu realmente gosto no dia-a-dia.


6

Poucos ou nenhum idioma tornam impossível causar efeitos colaterais. Idiomas completamente livres de efeitos colaterais seriam proibitivamente difíceis (quase impossíveis) de usar, exceto em uma capacidade muito limitada.

Por que efeitos colaterais são considerados maus?

Porque eles tornam muito mais difícil raciocinar sobre exatamente o que um programa faz e provar que ele faz o que você espera que ele faça.

Em um nível muito alto, imagine testar um site inteiro de três camadas com apenas testes de caixa preta. Claro, é factível, dependendo da escala. Mas certamente há muita duplicação acontecendo. E se não é um bug (que está relacionado a um efeito colateral), então você poderia quebrar todo o sistema para testes adicionais, até que o bug é diagnosticado e corrigido, ea correção é implantado no ambiente de teste.

Benefícios

Agora, reduza isso. Se você fosse bastante bom em escrever código livre de efeitos colaterais, quanto mais rápido seria em raciocinar sobre o que algum código existente fazia? Quanto mais rápido você poderia escrever testes de unidade? Como confiante você se sentiria que o código sem efeitos colaterais foi garantida livre de bugs, e que os usuários podem limitar a sua exposição a todos os erros que se têm?

Se o código não tiver efeitos colaterais, o compilador também poderá ter otimizações adicionais que ele poderá executar. Pode ser muito mais fácil implementar essas otimizações. Pode ser muito mais fácil conceber uma otimização para código livre de efeitos colaterais, o que significa que o fornecedor do compilador pode implementar otimizações que são difíceis de impossíveis no código com efeitos colaterais.

A simultaneidade também é drasticamente mais simples de implementar, gerar automaticamente e otimizar quando o código não tem efeitos colaterais. Isso ocorre porque todas as peças podem ser avaliadas com segurança em qualquer ordem. Permitir que os programadores escrevam códigos altamente concorrentes é amplamente considerado o próximo grande desafio que a Ciência da Computação precisa enfrentar e um dos poucos hedges restantes contra a Lei de Moore .


11
Ada torna muito difícil causar efeitos colaterais. Porém, não é impossível, mas você sabe claramente o que faz então.
Mouviciel 28/10/10

@mouviciel: Eu acho que existem pelo menos algumas línguas úteis por aí que tornam os efeitos colaterais muito difíceis e tentam relegá-los ao Mônada.
Merlyn Morgan-Graham

4

Os efeitos colaterais são como "vazamentos" em seu código que precisarão ser tratados posteriormente, por você ou por algum colega de trabalho desavisado.

Linguagens funcionais evitam variáveis ​​de estado e dados mutáveis ​​como uma maneira de tornar o código menos dependente do contexto e mais modular. A modularidade garante que o trabalho de um desenvolvedor não afete / prejudique o trabalho de outro.

Escalar a taxa de desenvolvimento com o tamanho da equipe é hoje um "santo graal" do desenvolvimento de software. Ao trabalhar com outros programadores, poucas coisas são tão importantes quanto a modularidade. Até os efeitos colaterais lógicos mais simples tornam a colaboração extremamente difícil.


+1 - "ou algum colega desavisado"
Merlyn Morgan-Graham

11
-1 para efeitos colaterais sendo "vazamentos que precisam ser manipulados". Criar "efeitos colaterais" (código funcional não puro) é o objetivo inteiro de escrever qualquer programa de computador não trivial.
Mason Wheeler

Este comentário vem seis anos depois, mas há efeitos colaterais e depois efeitos colaterais. O tipo desejável de efeitos colaterais, execução de E / S e assim por diante, é realmente necessário para qualquer programa, porque você precisa fornecer seus resultados ao usuário de alguma forma - mas os outros tipos de efeitos colaterais, nos quais seu código muda de estado sem um bom motivos como fazer E / S, são de fato um "vazamento" que precisará ser tratado mais tarde. A idéia básica é a separação entre comando e consulta : uma função que retorna um valor não deve ter um efeito colateral.
rmunn

4

Bem, IMHO, isso é bastante hipócrita. Ninguém gosta de efeitos colaterais, mas todo mundo precisa deles.

O que é tão perigoso quanto aos efeitos colaterais é que, se você chamar uma função, isso possivelmente terá um efeito não apenas no modo como a função se comportará quando for chamada da próxima vez, mas possivelmente terá esse efeito em outras funções. Assim, os efeitos colaterais introduzem um comportamento imprevisível e dependências não triviais.

Paradigmas de programação, como OO e funcional, abordam esse problema. OO reduz o problema impondo uma separação de preocupações. Isso significa que o estado do aplicativo, que consiste em muitos dados mutáveis, é encapsulado em objetos, cada um dos quais é responsável por manter apenas seu próprio estado. Dessa forma, o risco de dependências é reduzido e os problemas são muito mais isolados e fáceis de rastrear.

A programação funcional adota uma abordagem muito mais radical, onde o estado do aplicativo é simplesmente imutável da perspectiva do programador. É uma boa idéia, mas torna o idioma inútil por si só. Por quê? Porque QUALQUER operação de E / S tem efeitos colaterais. Assim que você ler de qualquer fluxo de entrada, é provável que o estado do aplicativo seja alterado, porque na próxima vez que você chamar a mesma função, é provável que o resultado seja diferente. Você pode estar lendo dados diferentes ou - também uma possibilidade - a operação pode falhar. O mesmo vale para a saída. Saída uniforme é uma operação com efeitos colaterais. Isso não é nada que você percebe hoje em dia, mas imagine que você tem apenas 20K para sua saída e, se produzir mais, seu aplicativo trava porque você está sem espaço em disco ou o que quer.

Então, sim, os efeitos colaterais são desagradáveis ​​e perigosos da perspectiva de um programador. A maioria dos bugs vem do modo como certas partes do estado do aplicativo são interligadas de maneira quase obscura, por meio de efeitos colaterais não considerados e muitas vezes desnecessários. Na perspectiva de um usuário, efeitos colaterais são o ponto de usar um computador. Eles não se importam com o que acontece dentro ou como é organizado. Eles fazem algo e esperam que o computador mude de acordo.


Curiosamente, a programação lógica não apenas não tem efeitos colaterais funcionais; mas você nem pode alterar o valor de uma variável depois de atribuída.
Ilan

@ Ian: Isso também é válido para algumas linguagens funcionais e é um estilo fácil de adotar.
back2dos

"A programação funcional adota uma abordagem muito mais radical, onde o estado do aplicativo é simplesmente imutável da perspectiva do programador. Essa é uma boa idéia, mas torna a linguagem inútil por si própria. Por quê? Porque QUALQUER operação de E / S tem lado efeitos ": o FP não proíbe efeitos colaterais, mas restringe-os quando não necessário. Por exemplo: (1) E / S -> efeitos colaterais são necessários; (2) computar uma função agregada a partir de uma sequência de valores -> efeito colateral (por exemplo, loop com variável acumuladora) não necessário.
Giorgio

2

Qualquer efeito colateral introduz parâmetros extras de entrada / saída que devem ser levados em consideração durante o teste.

Isso torna a validação do código muito mais complexa, pois o ambiente não pode ser limitado apenas ao código que está sendo validado, mas deve trazer parte ou todo o ambiente circundante (o global que é atualizado vive nesse código ali, que por sua vez depende disso código, que por sua vez depende de viver dentro de um servidor Java EE completo ....)

Ao tentar evitar efeitos colaterais, você limita a quantidade de externalismo necessária para executar o código.


1

Na minha experiência, um bom design na programação orientada a objetos exige o uso de funções que têm efeitos colaterais.

Por exemplo, considere um aplicativo básico da interface do usuário da área de trabalho. Talvez eu tenha um programa em execução que tenha em sua pilha um gráfico de objetos representando o estado atual do modelo de domínio do meu programa. As mensagens chegam aos objetos nesse gráfico (por exemplo, através de chamadas de métodos chamadas do controlador de camada da interface do usuário). O gráfico do objeto (modelo de domínio) na pilha é modificado em resposta às mensagens. Os observadores do modelo são informados sobre quaisquer alterações, a interface do usuário e talvez outros recursos sejam modificados.

Longe de ser ruim, o arranjo correto desses efeitos colaterais que modificam a pilha e a tela estão no centro do design do OO (neste caso, o padrão MVC).

Obviamente, isso não significa que seus métodos devam ter efeitos colaterais arbitrários. E as funções livres de efeito colateral têm um lugar na melhoria da legibilidade e, às vezes, do desempenho do seu código.


11
Os observadores (incluindo a interface do usuário) devem descobrir as modificações, assinando as mensagens / eventos que seu objeto envia. Este não é um efeito colateral, a menos que o objeto modifique diretamente o observador - o que seria um design ruim.
ChrisF

11
@ ChrisF Mais definitivamente é um efeito colateral. A mensagem passada para o observador (em um idioma OO provavelmente uma chamada de método em uma interface) levará ao estado do componente da interface do usuário na alteração de heap (e esses objetos de heap são visíveis para outras partes do programa). O componente da interface do usuário não é um parâmetro do método ou o valor de retorno. Em um sentido formal, para que uma função seja livre de efeitos colaterais, ela deve ser idempotente. A notificação no padrão MVC não é, por exemplo, a interface do usuário pode exibir uma lista de mensagens que recebeu - console - chamando duas vezes resulta em um estado de programa diferente.
Flamingpenguin 28/10/10

0

O mal é um pouco exagerado. Tudo depende do contexto do uso da linguagem.

Outra consideração aos já mencionados é que torna as provas de correção de um programa muito mais simples se não houver efeitos colaterais funcionais.


0

Como as perguntas acima apontaram, as linguagens funcionais não impedem tanto o código de causar efeitos colaterais, como fornecem ferramentas para gerenciar quais efeitos colaterais podem acontecer em um determinado pedaço de código e quando.

Isso acaba tendo consequências muito interessantes. Primeiro, e mais obviamente, existem inúmeras coisas que você pode fazer com o código livre de efeitos colaterais, que já foi descrito. Mas há outras coisas que também podemos fazer, mesmo ao trabalhar com código que tenha efeitos colaterais:

  • Em código com estado mutável, podemos gerenciar o escopo do estado de forma a garantir estaticamente que ele não possa vazar para fora de uma determinada função, o que nos permite coletar lixo sem os esquemas de contagem de referência ou de estilo de marcação e varredura , ainda assim, verifique se nenhuma referência sobrevive. As mesmas garantias também são úteis para manter informações confidenciais, etc. (Isso pode ser obtido usando a mônada ST em haskell)
  • Ao modificar o estado compartilhado em vários encadeamentos, podemos evitar a necessidade de bloqueios rastreando alterações e executando uma atualização atômica no final de uma transação, ou revertendo a transação e repetindo-a se outro encadeamento fizer uma modificação conflitante. Isso só é possível porque podemos garantir que o código não tenha outros efeitos além das modificações de estado (que podemos abandonar alegremente). Isso é realizado pela mônada do STM (Software Transactional Memory) em Haskell.
  • podemos rastrear os efeitos do código e colocá-lo em uma caixa de areia trivial, filtrando os efeitos que ele possa precisar para garantir sua segurança, permitindo, por exemplo, que o código digitado pelo usuário seja executado com segurança em um site

0

Em bases de código complexas, interações complexas de efeitos colaterais são a coisa mais difícil sobre a qual acho. Só posso falar pessoalmente, dada a forma como meu cérebro funciona. Efeitos colaterais, estados persistentes, entradas mutantes e assim por diante me fazem pensar sobre "quando" e "onde" as coisas raciocinam sobre a correção, e não apenas "o que" está acontecendo em cada função individual.

Não consigo me concentrar apenas no "o quê". Não posso concluir, depois de testar minuciosamente uma função que causa efeitos colaterais, que espalhe um ar de confiabilidade por todo o código que a utiliza, pois os chamadores ainda podem abusar dela chamando-a na hora errada, no encadeamento errado, no errado ordem. Enquanto isso, uma função que não causa efeitos colaterais e apenas retorna uma nova saída dada uma entrada (sem tocar na entrada) é praticamente impossível de usar dessa maneira.

Mas eu sou do tipo pragmático, acho, ou pelo menos tento ser, e não acho que tenhamos necessariamente de eliminar todos os efeitos colaterais ao mínimo possível para raciocinar sobre a correção do nosso código (no mínimo Eu acharia isso muito difícil de fazer em idiomas como C). Onde acho muito difícil argumentar sobre a correção é quando temos a combinação de fluxos de controle complexos e efeitos colaterais.

Fluxos de controle complexos para mim são os de natureza gráfica, geralmente recursivos ou recursivos (filas de eventos, por exemplo, que não estão chamando diretamente os eventos recursivamente, mas são "recursivos" na natureza), talvez fazendo coisas no processo de atravessar uma estrutura de gráfico vinculada real ou processar uma fila de eventos não homogênea que contém uma mistura eclética de eventos a serem processados, levando-nos a todos os tipos de partes diferentes da base de código e a todos os efeitos colaterais diferentes. Se você tentasse desenhar todos os lugares em que acabaria no código, ele se pareceria com um gráfico complexo e, potencialmente, com nós no gráfico que você nunca imaginou que estariam lá naquele momento, e considerando que eles são todos causando efeitos colaterais,

As linguagens funcionais podem ter fluxos de controle extremamente complexos e recursivos, mas o resultado é tão fácil de entender em termos de correção, porque não existem todos os tipos de efeitos colaterais ecléticos ocorrendo no processo. É somente quando fluxos de controle complexos encontram efeitos colaterais ecléticos que considero indutor de dor de cabeça tentar compreender a totalidade do que está acontecendo e se sempre faz a coisa certa.

Portanto, quando tenho esses casos, geralmente acho muito difícil, se não impossível, me sentir muito confiante sobre a correção de tal código, muito menos muito confiante de que posso fazer alterações nesse código sem tropeçar em algo inesperado. Portanto, a solução para mim é simplificar o fluxo de controle ou minimizar / unificar os efeitos colaterais (ao unificar, quero dizer, causar apenas um tipo de efeito colateral a muitas coisas durante uma fase específica do sistema, não dois, três ou três). dúzia). Eu preciso que uma dessas duas coisas aconteça para permitir que meu cérebro simplório se sinta confiante sobre a correção do código que existe e a correção das alterações que apresento. É muito fácil ter certeza da correção do código que introduz efeitos colaterais, se os efeitos colaterais são uniformes e simples, juntamente com o fluxo de controle, da seguinte forma:

for each pixel in an image:
    make it red

É muito fácil argumentar sobre a correção desse código, mas principalmente porque os efeitos colaterais são muito uniformes e o fluxo de controle é muito simples. Mas digamos que tivemos um código como este:

for each vertex to remove in a mesh:
     start removing vertex from connected edges():
         start removing connected edges from connected faces():
             rebuild connected faces excluding edges to remove():
                  if face has less than 3 edges:
                       remove face
             remove edge
         remove vertex

Então, esse é um pseudocódigo ridiculamente simplificado, que normalmente envolveria muito mais funções e loops aninhados e muito mais coisas que teriam que continuar (atualizando vários mapas de textura, pesos ósseos, estados de seleção etc.), mas mesmo o pseudocódigo torna tão difícil razão da correção por causa da interação do fluxo de controle complexo tipo gráfico e dos efeitos colaterais que estão ocorrendo. Portanto, uma estratégia para simplificar é adiar o processamento e focar apenas em um tipo de efeito colateral por vez:

for each vertex to remove:
     mark connected edges
for each marked edge:
     mark connected faces
for each marked face:
     remove marked edges from face
     if num_edges < 3:
          remove face

for each marked edge:
     remove edge
for each vertex to remove:
     remove vertex

... algo nesse sentido como uma iteração de simplificação. Isso significa que estamos passando os dados várias vezes, o que definitivamente gera um custo computacional, mas geralmente descobrimos que podemos multithread desse código resultante com mais facilidade, agora que os efeitos colaterais e os fluxos de controle assumiram essa natureza uniforme e mais simples. Além disso, cada loop pode ser mais amigável ao cache do que atravessar o gráfico conectado e causar efeitos colaterais à medida que avançamos (por exemplo: use um conjunto de bits paralelos para marcar o que precisa ser atravessado, para que possamos fazer as passagens diferidas em ordem sequencial classificada usando bitmasks e FFS). Mas o mais importante é que acho a segunda versão muito mais fácil de raciocinar em termos de correção, além de alterar sem causar bugs. De modo a'

E, afinal, precisamos que efeitos colaterais ocorram em algum momento, ou então teríamos apenas funções que geram dados sem ter para onde ir. Frequentemente, precisamos gravar algo em um arquivo, exibir algo em uma tela, enviar os dados por um soquete, algo desse tipo e todas essas coisas são efeitos colaterais. Mas podemos definitivamente reduzir o número de efeitos colaterais supérfluos que ocorrem e também reduzir o número de efeitos colaterais quando os fluxos de controle são muito complicados, e acho que seria muito mais fácil evitar erros se o fizéssemos.


-1

Isso não é mau. Na minha opinião, é necessário distinguir os dois tipos de funções - com efeitos colaterais e sem. A função sem efeitos colaterais: - retorna sempre o mesmo com os mesmos argumentos; portanto, por exemplo, essa função sem argumentos não faz sentido. - Isso também significa que a ordem na qual algumas dessas funções são chamadas não desempenha nenhum papel - deve poder ser executada e pode ser depurada apenas (!), Sem nenhum outro código. E agora, lol, veja o que o JUnit faz. Uma função com efeitos colaterais: - possui uma espécie de "vazamentos", o que pode ser destacado automaticamente - é muito importante depurando e procurando erros, o que geralmente é causado por efeitos colaterais. - Qualquer função com efeitos colaterais também possui uma "parte" de si mesma sem efeitos colaterais, o que também pode ser separado automaticamente. Tão maus são esses efeitos colaterais,


isso parece não oferecer nada substancial sobre os pontos levantados e explicados nas 12 respostas anteriores #
30156
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.