Eu posso ver os benefícios de objetos mutáveis vs imutáveis, como objetos imutáveis, que levam muito tempo para solucionar problemas na programação multiencadeada devido ao estado compartilhado e gravável. Pelo contrário, objetos mutáveis ajudam a lidar com a identidade do objeto, em vez de criar uma nova cópia todas as vezes e, assim, também melhoram o desempenho e o uso da memória, especialmente para objetos maiores.
Uma coisa que estou tentando entender é o que pode dar errado em ter objetos mutáveis no contexto da programação funcional. Como um dos pontos que me disseram é que o resultado de chamar funções em ordem diferente não é determinístico.
Estou procurando um exemplo concreto real, onde é muito evidente o que pode dar errado usando objetos mutáveis na programação de funções. Basicamente, se é ruim, é ruim, independentemente do OO ou paradigma de programação funcional, certo?
Creio que abaixo da minha própria declaração responde a essa pergunta. Mas ainda preciso de um exemplo para que eu possa senti-lo mais naturalmente.
OO ajuda a gerenciar a dependência e a escrever programas mais fáceis e de manutenção com a ajuda de ferramentas como encapsulamento, polimorfismo etc.
A programação funcional também tem o mesmo motivo de promover código sustentável, mas usando o estilo que elimina a necessidade de usar ferramentas e técnicas de OO - uma das quais acredito ser a minimização de efeitos colaterais, funções puras etc.
Respostas:
Eu acho que a importância é melhor demonstrada comparando com uma abordagem OO
por exemplo, digamos que temos um objeto
No paradigma OO, o método é anexado aos dados e faz sentido que esses dados sejam alterados pelo método.
No Paradigma Funcional, definimos um resultado em termos da função. um pedido adquirido É o resultado da função de compra aplicada a um pedido. Isso implica algumas coisas das quais precisamos ter certeza
Você esperaria order.Status == "Comprado"?
Isso também implica que nossas funções são idempotentes. ie executá-los duas vezes deve produzir o mesmo resultado cada vez.
Se o pedido for alterado pela função de compra, o PurchaseOrder2 falhará.
Ao definir as coisas como resultados de funções, nos permite usar esses resultados sem realmente calculá-los. Que, em termos de programação, é adiada para execução.
Isso pode ser útil por si só, mas uma vez que não temos certeza sobre quando uma função realmente acontecerá E estamos bem com isso, podemos aproveitar o processamento paralelo muito mais do que em um paradigma OO.
Sabemos que executar uma função não afetará os resultados de outra função; para que possamos deixar o computador para executá-los na ordem que desejar, usando quantos threads desejar.
Se uma função modifica sua entrada, temos que ter muito mais cuidado com essas coisas.
fonte
Order Purchase() { return new Order(Status = "Purchased") }
para que o status seja campo somente leitura. ? Novamente, por que essa prática é mais relevante no contexto do paradigma de programação de funções? Os benefícios que você mencionou também podem ser vistos na programação OO, certo?A chave para entender por que objetos imutáveis são benéficos não está na tentativa de encontrar exemplos concretos no código funcional. Como a maioria dos códigos funcionais é escrita usando linguagens funcionais, e a maioria das linguagens funcionais é imutável por padrão, a própria natureza do paradigma é projetada para evitar que o que você está procurando aconteça.
O principal a perguntar é: qual é esse benefício da imutabilidade? A resposta é: evita a complexidade. Digamos que temos duas variáveis
x
ey
. Ambos começam com o valor de1
.y
embora dobre a cada 13 segundos. Qual será o valor de cada um deles em 20 dias?x
será1
. Isso é fácil. No entanto, seria necessário trabalhary
, pois é muito mais complexo. Que hora do dia em 20 dias? Preciso levar em consideração o horário de verão? A complexidade doy
versusx
é muito mais.E isso ocorre no código real também. Toda vez que você adiciona um valor mutante à mistura, isso se torna outro valor complexo para você manter e calcular na sua cabeça ou no papel ao tentar escrever, ler ou depurar o código. Quanto mais complexidade, maior a chance de você cometer um erro e introduzir um bug. O código é difícil de escrever; difícil de ler; difícil de depurar: é difícil acertar o código.
A mutabilidade não é ruim . Um programa com mutabilidade zero não pode ter resultado, o que é bastante inútil. Mesmo que a mutabilidade seja gravar um resultado na tela, disco ou qualquer outra coisa, ele precisa estar lá. O que é ruim é complexidade desnecessária. Uma das maneiras mais simples de reduzir a complexidade é tornar as coisas imutáveis por padrão e torná-las mutáveis quando necessário, devido a razões de desempenho ou funcionais.
fonte
y
precisa sofrer mutação; isso é um requisito. Às vezes, precisamos ter um código complexo para atender a requisitos complexos. O que eu estava tentando enfatizar é que complexidade desnecessária deve ser evitada. Os valores mutantes são inerentemente mais complexos que os fixos; portanto, para evitar complexidade desnecessária, apenas modifique valores quando for necessário.As mesmas coisas que podem dar errado na programação não funcional: você pode obter efeitos colaterais inesperados e indesejados , que é uma causa bem conhecida de erros desde a invenção das linguagens de programação com escopo definido.
IMHO, a única diferença real entre programação funcional e não-funcional é que, no código não-funcional, você normalmente espera efeitos colaterais; na programação funcional, não.
Claro - efeitos colaterais indesejados são uma categoria de bugs, independentemente do paradigma. O oposto também é verdadeiro - efeitos colaterais deliberadamente usados podem ajudar a lidar com problemas de desempenho e são normalmente necessários para a maioria dos programas do mundo real quando se trata de E / S e de sistemas externos - também independentemente do paradigma.
fonte
Acabei de responder a uma pergunta do StackOverflow que ilustra bem a sua pergunta. O principal problema das estruturas de dados mutáveis é que sua identidade é válida apenas em um instante exato no tempo, portanto as pessoas tendem a se concentrar o máximo possível no pequeno ponto do código em que sabem que a identidade é constante. Neste exemplo em particular, ele faz muito log dentro de um loop for:
Quando você está acostumado à imutabilidade, não há medo de que a estrutura de dados seja alterada se você esperar demais, para que você possa executar tarefas que são logicamente separadas à sua vontade, de uma maneira muito mais dissociada:
fonte
A vantagem de usar objetos imutáveis é que, se alguém recebe uma referência a um objeto com uma certa propriedade quando o destinatário a examina e precisa fornecer a algum outro código uma referência a um objeto com a mesma propriedade, pode simplesmente passar ao longo da referência ao objeto, sem levar em consideração quem mais poderia ter recebido a referência ou o que eles poderiam fazer com o objeto [já que não há mais ninguém que possa fazer com o objeto] ou quando o receptor pode examiná-lo [uma vez que todos as propriedades serão as mesmas, independentemente de quando forem examinadas].
Por outro lado, o código que precisa fornecer a alguém uma referência a um objeto mutável que terá uma certa propriedade quando o receptor a examinar (supondo que o próprio receptor não a mude) também precisa saber que nada além do receptor jamais mudará essa propriedade, ou saiba quando o destinatário acessará essa propriedade e saiba que nada mudará essa propriedade até a última vez que o destinatário a examinar.
Eu acho que é muito útil para a programação em geral (não apenas a programação funcional) pensar em objetos imutáveis como se estivessem em três categorias:
Objetos que não podem permitir que nada os altere, mesmo com uma referência. Tais objetos e referências a eles se comportam como valores e podem ser compartilhados livremente.
Objetos que permitiriam ser alterados por código que tenha referências a eles, mas cujas referências nunca serão expostas a nenhum código que realmente os alteraria. Esses objetos encapsulam valores, mas eles só podem ser compartilhados com códigos confiáveis, para não alterá-los ou expô-los ao código que possa funcionar.
Objetos que serão alterados. Esses objetos são melhor visualizados como contêineres e referências a eles como identificadores .
Um padrão útil é geralmente fazer com que um objeto crie um contêiner, preencha-o usando um código confiável para não manter uma referência posteriormente e, em seguida, tenha as únicas referências que já existirem em qualquer lugar do universo, em código que nunca modificará o objeto uma vez preenchido. Embora o contêiner possa ser de um tipo mutável, ele pode ser fundamentado em (*) como se fosse imutável, pois nada jamais o modificaria. Se todas as referências ao contêiner forem mantidas em tipos de invólucros imutáveis que nunca alterem seu conteúdo, esses invólucros poderão ser passados com segurança, como se os dados contidos nele fossem mantidos em objetos imutáveis, pois as referências aos invólucros podem ser compartilhadas e examinadas livremente em a qualquer momento.
(*) No código multithread, pode ser necessário usar "barreiras de memória" para garantir que, antes que qualquer thread possa ver qualquer referência ao wrapper, os efeitos de todas as ações no contêiner sejam visíveis para esse thread, mas esse é um caso especial mencionado aqui apenas para completar.
fonte
Como já foi mencionado, o problema com o estado mutável é basicamente uma subclasse do maior problema de efeitos colaterais , onde o tipo de retorno de uma função não descreve com precisão o que a função realmente faz, porque, neste caso, também faz mutação de estado. Esse problema foi solucionado por algumas novas linguagens de pesquisa, como a F * ( http://www.fstar-lang.org/tutorial/ ). Essa linguagem cria um sistema de efeitos semelhante ao sistema de tipos, onde uma função não apenas declara estaticamente seu tipo, mas também seus efeitos. Dessa forma, os chamadores da função estão cientes de que uma mutação de estado pode ocorrer ao chamar a função e esse efeito é propagado para os chamadores.
fonte