Eu me vejo usando tipos cada vez mais imutáveis quando não se espera que as instâncias da classe sejam alteradas . Requer mais trabalho (veja o exemplo abaixo), mas facilita o uso dos tipos em um ambiente multithread.
Ao mesmo tempo, raramente vejo tipos imutáveis em outros aplicativos, mesmo quando a mutabilidade não beneficia ninguém.
Pergunta: Por que tipos imutáveis são tão raramente usados em outras aplicações?
- Isso ocorre porque é mais demorado escrever código para um tipo imutável,
- Ou estou faltando alguma coisa e há algumas desvantagens importantes ao usar tipos imutáveis?
Exemplo da vida real
Digamos que você obtenha Weather
uma API RESTful assim:
public Weather FindWeather(string city)
{
// TODO: Load the JSON response from the RESTful API and translate it into an instance
// of the Weather class.
}
O que geralmente veríamos é (novas linhas e comentários removidos para encurtar o código):
public sealed class Weather
{
public City CorrespondingCity { get; set; }
public SkyState Sky { get; set; } // Example: SkyState.Clouds, SkyState.HeavySnow, etc.
public int PrecipitationRisk { get; set; }
public int Temperature { get; set; }
}
Por outro lado, eu escreveria dessa maneira, considerando que obter uma Weather
API e modificá-la seria estranho: mudar Temperature
ou Sky
não mudar o clima no mundo real, e mudar CorrespondingCity
também não faz sentido.
public sealed class Weather
{
private readonly City correspondingCity;
private readonly SkyState sky;
private readonly int precipitationRisk;
private readonly int temperature;
public Weather(City correspondingCity, SkyState sky, int precipitationRisk,
int temperature)
{
this.correspondingCity = correspondingCity;
this.sky = sky;
this.precipitationRisk = precipitationRisk;
this.temperature = temperature;
}
public City CorrespondingCity { get { return this.correspondingCity; } }
public SkyState Sky { get { return this.sky; } }
public int PrecipitationRisk { get { return this.precipitationRisk; } }
public int Temperature { get { return this.temperature; } }
}
fonte
{get; private set;}
, e mesmo a variável mutável deve ter um construtor, porque todos esses campos devem sempre ser definidos e por que você não aplicaria isso? Fazer essas duas mudanças perfeitamente razoáveis os leva à paridade de recursos e LoC.Respostas:
Eu programa em C # e Objective-C. Gosto muito de digitar imutável, mas na vida real sempre fui forçado a limitar seu uso, principalmente para tipos de dados, pelos seguintes motivos:
StringBuilder
ouUriBuilder
em C # ouWeatherBuilder
no seu caso. Esse é o principal motivo para mim, pois muitas classes que eu design não valem esse esforço.Em resumo, a imutabilidade é boa para objetos que se comportam como valores ou que possuem apenas algumas propriedades. Antes de tornar qualquer coisa imutável, você deve considerar o esforço necessário e a usabilidade da própria classe depois de torná-la imutável.
fonte
Geralmente, tipos imutáveis criados em linguagens que não giram em torno da imutabilidade tenderão a custar mais tempo para o desenvolvedor criar e potencialmente usar se exigirem algum tipo de objeto "construtor" para expressar as alterações desejadas (isso não significa que o o trabalho será maior, mas há um custo inicial nesses casos). Além disso, independentemente de o idioma facilitar a criação de tipos imutáveis ou não, ele sempre exigirá algum processamento e sobrecarga de memória para tipos de dados não triviais.
Tornando as funções desprovidas de efeitos colaterais
Se você trabalha em linguagens que não giram em torno da imutabilidade, acho que a abordagem pragmática não é a de tornar imutável todo tipo de dados. Uma mentalidade potencialmente muito mais produtiva que oferece muitos dos mesmos benefícios é se concentrar em maximizar o número de funções em seu sistema que causam zero efeitos colaterais .
Como um exemplo simples, se você tem uma função que causa um efeito colateral como este:
Então não precisamos de um tipo de dados inteiro imutável que proíba operadores como atribuição de pós-inicialização para fazer com que essa função evite efeitos colaterais. Podemos simplesmente fazer isso:
Agora a função não mexe com
x
nada fora do seu escopo e, nesse caso trivial, podemos até raspar alguns ciclos, evitando qualquer sobrecarga associada ao indireção / alias. No mínimo, a segunda versão não deve ser mais cara em termos de computação que a primeira.Coisas caras para copiar na íntegra
É claro que a maioria dos casos não é tão trivial se queremos evitar que uma função cause efeitos colaterais. Um caso de uso complexo do mundo real pode ser mais ou menos assim:
Nesse ponto, a malha pode exigir algumas centenas de megabytes de memória com mais de cem mil polígonos, ainda mais vértices e arestas, vários mapas de textura, destinos de metamorfose etc. Seria muito caro copiar essa malha inteira apenas para fazer isso.
transform
função livre de efeitos colaterais, assim:E é nesses casos que copiar algo na sua totalidade normalmente seria uma sobrecarga épica, onde eu achei útil transformar
Mesh
uma estrutura de dados persistente e um tipo imutável com o "construtor" analógico para criar versões modificadas para que pode simplesmente copiar superficialmente e contar referências de peças que não são únicas. É tudo com o foco de poder escrever funções de malha livres de efeitos colaterais.Estruturas de dados persistentes
E nesses casos em que copiar tudo é tão incrivelmente caro, achei o esforço de projetar um imutável
Mesh
para realmente render, mesmo que tivesse um custo inicial bastante alto , porque não simplificava apenas a segurança do encadeamento. Ele também simplificou a edição não destrutiva (permitindo que o usuário estratifique operações de malha sem modificar sua cópia original), desfaz sistemas (agora o sistema desfazer pode apenas armazenar uma cópia imutável da malha antes das alterações feitas por uma operação sem explodir a memória use) e exceção-safety (agora, se ocorrer uma exceção na função acima, a função não precisará reverter e desfazer todos os efeitos colaterais, pois não causou nenhum).Nesses casos, posso dizer com confiança que o tempo necessário para tornar essas pesadas estruturas de dados imutáveis economizou mais tempo do que custa, pois comparei os custos de manutenção desses novos designs com os anteriores, que giravam em torno da mutabilidade e das funções que causavam efeitos colaterais, e os projetos mutáveis anteriores custam muito mais tempo e são muito mais propensos a erros humanos, especialmente em áreas que são realmente tentadoras para os desenvolvedores negligenciarem durante o tempo de crise, como segurança de exceção.
Então, acho que tipos de dados imutáveis realmente valem a pena nesses casos, mas nem tudo precisa ser imutável para tornar a maioria das funções em seu sistema livre de efeitos colaterais. Muitas coisas são baratas o suficiente para copiar apenas na íntegra. Além disso, muitos aplicativos do mundo real precisarão causar alguns efeitos colaterais aqui e ali (no mínimo, como salvar um arquivo), mas normalmente existem muito mais funções que podem ser desprovidas de efeitos colaterais.
O ponto de ter alguns tipos de dados imutáveis para mim é garantir que possamos escrever o número máximo de funções para ficar livre de efeitos colaterais, sem incorrer em sobrecarga épica na forma de copiar em profundidade estruturas maciças de dados, à esquerda e à direita na íntegra, quando apenas pequenas porções deles precisam ser modificados. A existência de estruturas de dados persistentes nesses casos acaba se tornando um detalhe de otimização para permitir que escrevamos nossas funções para estar livre de efeitos colaterais, sem pagar um custo épico para fazê-lo.
Sobrecarga imutável
Agora, conceitualmente, as versões mutáveis sempre terão uma vantagem em eficiência. Sempre há essa sobrecarga computacional associada a estruturas de dados imutáveis. Mas achei uma troca digna nos casos que descrevi acima e você pode se concentrar em tornar a sobrecarga suficientemente mínima por natureza. Eu prefiro esse tipo de abordagem em que a correção se torna fácil e a otimização se torna mais difícil do que a otimização sendo mais fácil, mas a correção se torna mais difícil. Não é tão desmoralizante ter um código que funcione perfeitamente corretamente, necessitando de mais ajustes sobre o código que não funcione corretamente em primeiro lugar, não importa a rapidez com que obtém seus resultados incorretos.
fonte
A única desvantagem em que consigo pensar é que, em teoria, o uso de dados imutáveis pode ser mais lento que os mutáveis - é mais lento criar uma nova instância e coletar uma anterior do que modificar uma existente.
O outro "problema" é que você não pode usar apenas tipos imutáveis. No final, você precisa descrever o estado e usar tipos mutáveis para fazer isso - sem alterar o estado, você não pode fazer nenhum trabalho.
Mas a regra geral ainda é usar tipos imutáveis sempre que possível e tornar os tipos mutáveis apenas quando realmente houver uma razão para fazê-lo ...
E para responder à pergunta " Por que tipos imutáveis são tão raramente usados em outros aplicativos? " - Eu realmente não acho que eles estejam ... onde quer que você olhe, todos recomendam tornar suas aulas tão imutáveis quanto possível ... por exemplo: http://www.javapractices.com/topic/TopicAction.do?Id=29
fonte
Car
objeto mutável é atualizado continuamente com a localização de um automóvel físico específico, se eu tiver uma referência a esse objeto, posso descobrir o paradeiro desse automóvel de maneira rápida e fácil. SeCar
fosse imutável, encontrar o paradeiro atual provavelmente seria muito mais difícil.Para modelar qualquer sistema do mundo real onde as coisas possam mudar, o estado mutável precisará ser codificado em algum lugar, de alguma forma. Existem três maneiras principais de um objeto manter um estado mutável:
O uso primeiro facilita para um objeto fazer uma captura instantânea imutável do estado atual. O uso do segundo facilita para um objeto criar uma exibição ao vivo do estado atual. Às vezes, o uso da terceira pode tornar certas ações mais eficientes nos casos em que há pouca necessidade esperada de instantâneos imutáveis nem exibições ao vivo.
Além do fato de que a atualização do estado armazenado usando uma referência mutável para um objeto imutável geralmente é mais lenta do que a atualização do estado armazenado usando um objeto mutável, o uso de uma referência mutável exigirá que você renuncie à possibilidade de construir uma exibição ao vivo barata do estado. Se não for necessário criar uma exibição ao vivo, isso não é um problema; se, no entanto, for necessário criar uma exibição ao vivo, a incapacidade de usar uma referência imutável fará todas as operações com a exibição - lê e grava- muito mais lento do que seria. Se a necessidade de capturas instantâneas imutáveis exceder a necessidade de exibições ao vivo, o desempenho aprimorado de capturas instantâneas imutáveis pode justificar o impacto no desempenho de exibições ao vivo, mas se alguém precisar de exibições ao vivo e não precisar de capturas de tela, é o caminho que faz referências imutáveis a objetos mutáveis. ir.
fonte
No seu caso, a resposta é principalmente porque o C # tem um suporte insuficiente à imutabilidade ...
Seria ótimo se:
tudo será imutável por padrão, a menos que indicado de outra forma (por exemplo, com uma palavra-chave 'mutável'), misturar tipos imutáveis e mutáveis é confuso
mutação métodos (
With
) estará automaticamente disponível - embora isso já pode ser conseguido ver Comhaverá uma maneira de dizer que o resultado de uma chamada de método específica (ou seja
ImmutableList<T>.Add
) não pode ser descartado ou, pelo menos, produzirá um avisoE principalmente se o compilador puder garantir o máximo possível a imutabilidade quando solicitado (consulte https://github.com/dotnet/roslyn/issues/159 )
fonte
MustUseReturnValueAttribute
atributo personalizado que faz exatamente isso.PureAttribute
tem o mesmo efeito e é ainda melhor para isso.Ignorância? Inexperiência?
Objetos imutáveis são hoje amplamente considerados superiores, mas é um desenvolvimento relativamente recente. Os engenheiros que não se mantiveram atualizados ou estão simplesmente presos no que sabem não os usarão. E são necessárias algumas alterações no design para usá-las efetivamente. Se os aplicativos são antigos ou os engenheiros são fracos em habilidades de design, usá-los pode ser estranho ou problemático.
fonte