Há alguma orientação no Scala sobre quando usar val com uma coleção mutável versus usar var com uma coleção imutável? Ou você realmente deveria ter como objetivo val com uma coleção imutável?
O fato de haver os dois tipos de coleção me dá muitas opções e, muitas vezes, não sei como fazer essa escolha.
scala
collections
functional-programming
immutability
James McCabe
fonte
fonte
Respostas:
Pergunta bastante comum, esta. O difícil é encontrar as duplicatas.
Você deve se esforçar para obter transparência referencial . O que isso significa é que, se eu tiver uma expressão "e", posso fazer um
val x = e
e substituire
porx
. Essa é a propriedade que a mutabilidade quebra. Sempre que você precisar tomar uma decisão de design, maximize para transparência referencial.Na prática, um método-local
var
é o mais segurovar
que existe, pois não foge ao método. Se o método for curto, melhor ainda. Se não estiver, tente reduzi-lo extraindo outros métodos.Por outro lado, uma coleção mutável tem o potencial de escapar, mesmo que não. Ao alterar o código, você pode querer passá-lo para outros métodos ou retorná-lo. Esse é o tipo de coisa que quebra a transparência referencial.
Em um objeto (um campo), praticamente a mesma coisa acontece, mas com consequências mais terríveis. De qualquer forma, o objeto terá estado e, portanto, quebrará a transparência referencial. Mas ter uma coleção mutável significa que até o próprio objeto pode perder o controle de quem o está alterando.
fonte
immutable val
sobreimmutable var
sobremutable val
sobremutable var
. Principalmenteimmutable var
acabadomutable val
!var
. Outro recurso interessante de usar coleções imutáveis é que você pode manter cópias antigas com eficiência, mesmo se houvervar
mutação.var x: Set[Int]
mais deval x: mutable.Set[Int]
uma vez se você passarx
para alguma outra função, no primeiro caso, você tem certeza, essa função não pode sofrer mutaçãox
para você.Se você trabalha com coleções imutáveis e precisa "modificá-las", por exemplo, adicionar elementos a elas em um loop, você deve usar
var
s porque precisa armazenar a coleção resultante em algum lugar. Se você ler apenas coleções imutáveis, useval
s.Em geral, certifique-se de não confundir referências e objetos.
val
s são referências imutáveis (ponteiros constantes em C). Ou seja, ao usarval x = new MutableFoo()
, você poderá alterar o objeto para o qualx
aponta, mas não poderá alterar para qual objetox
aponta. O oposto é válido se você usarvar x = new ImmutableFoo()
. Pegando meu conselho inicial: se você não precisa mudar para qual objeto um ponto de referência, useval
s.fonte
var immutable = something(); immutable = immutable.update(x)
anula o propósito de usar uma coleção imutável. Você já desistiu da transparência referencial e normalmente pode obter o mesmo efeito de uma coleção mutável com melhor complexidade de tempo. Das quatro possibilidades (val
evar
, mutável e imutável), esta faz menos sentido. Costumo usarval mutable
.var list: List[X] = Nil; list = item :: list; ...
e esqueci que uma vez escrevi de forma diferente.A melhor maneira de responder é com um exemplo. Suponha que temos algum processo simplesmente coletando números por algum motivo. Desejamos registrar esses números e enviaremos a coleção para outro processo para fazer isso.
Claro, ainda estamos coletando números depois de enviarmos a coleção para o logger. E digamos que haja alguma sobrecarga no processo de registro que atrasa o registro real. Espero que você possa ver onde isso vai dar.
Se armazenarmos esta coleção em um mutável
val
, (mutável porque estamos continuamente adicionando a ele), isso significa que o processo que faz o registro estará olhando para o mesmo objeto que ainda está sendo atualizado pelo nosso processo de coleção. Essa coleção pode ser atualizada a qualquer momento e, portanto, quando for hora de registrar, podemos não estar realmente registrando a coleção que enviamos.Se usarmos um imutável
var
, enviaremos uma estrutura de dados imutável para o logger. Quando adicionarmos mais números à nossa coleção, estaremos substituindo o nossovar
por uma nova estrutura de dados imutável . Isso não significa que a coleção enviada ao logger seja substituída! Ainda faz referência à coleção que foi enviada. Portanto, nosso logger irá de fato registrar a coleção que recebeu.fonte
Acho que os exemplos nesta postagem do blog vão esclarecer melhor, pois a questão de qual combo usar se torna ainda mais importante em cenários de simultaneidade: a importância da imutabilidade para a simultaneidade . E já que estamos nisso, observe o uso preferencial de synchronized vs @volatile vs algo como AtomicReference: três ferramentas
fonte
var immutable
vs.val mutable
Além de muitas respostas excelentes para essa pergunta. Aqui está um exemplo simples, que ilustra os perigos potenciais de
val mutable
:Objetos mutáveis podem ser modificados dentro de métodos, que os tomam como parâmetros, enquanto a reatribuição não é permitida.
Resultado:
Algo assim não pode acontecer com
var immutable
, porque a reatribuição não é permitida:Resulta em:
Uma vez que os parâmetros de função são tratados como
val
essa reatribuição não é permitida.fonte
mutable val
não é possível com oimmutable var
. O que aqui está incorreto?+=
método como o buffer de matriz. Suas respostas indicam que+=
é o mesmox = x + y
que não é. Sua declaração de que os parâmetros de função são tratados como vals está correta e você obtém o erro que mencionou, mas apenas porque usou=
. Você pode obter o mesmo erro com um ArrayBuffer, portanto, a mutabilidade das coleções aqui não é realmente relevante. Portanto, não é uma boa resposta porque não chega ao que o OP está falando. Embora seja um bom exemplo dos perigos de passar uma coleção mutável por aí se você não tivesse essa intenção.ArrayBuffer
usandoVector
. A pergunta do OP é ampla, mas eles estavam procurando sugestões de quando usá-la, então acredito que minha resposta seja útil porque ilustra os perigos de passar uma coleção mutável (o fato de serval
não ajuda);immutable var
é mais seguro do quemutable val
.