val-mutável versus var-imutável em Scala

97

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.

James McCabe
fonte
Veja por exemplo stackoverflow.com/questions/10999024/…
Luigi Plinge

Respostas:

104

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 = ee substituir epor x. 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 seguro varque 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.

Daniel C. Sobral
fonte
38
Nice, New grande imagem na minha mente: Prefere immutable valsobre immutable varsobre mutable valsobre mutable var. Principalmente immutable varacabado mutable val!
Peter Schmitz
2
Lembre-se de que você ainda pode fechar (como vazar uma "função" de efeito colateral que pode alterá-lo) em um local mutável var. Outro recurso interessante de usar coleções imutáveis ​​é que você pode manter cópias antigas com eficiência, mesmo se houver varmutação.
Misterioso Dan
1
tl; dr: prefira var x: Set[Int] mais de val x: mutable.Set[Int] uma vez se você passar xpara alguma outra função, no primeiro caso, você tem certeza, essa função não pode sofrer mutação xpara você.
pathikrit
17

Se você trabalha com coleções imutáveis ​​e precisa "modificá-las", por exemplo, adicionar elementos a elas em um loop, você deve usar vars porque precisa armazenar a coleção resultante em algum lugar. Se você ler apenas coleções imutáveis, use vals.

Em geral, certifique-se de não confundir referências e objetos. vals são referências imutáveis ​​(ponteiros constantes em C). Ou seja, ao usar val x = new MutableFoo(), você poderá alterar o objeto para o qual xaponta, mas não poderá alterar para qual objeto x aponta. O oposto é válido se você usar var x = new ImmutableFoo(). Pegando meu conselho inicial: se você não precisa mudar para qual objeto um ponto de referência, use vals.

Malte Schwerhoff
fonte
1
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 ( vale var, mutável e imutável), esta faz menos sentido. Costumo usar val mutable.
Jim Pivarski
3
@JimPivarski Eu discordo, assim como os outros, veja a resposta do Daniel e o comentário do Peter. Se você precisar atualizar uma estrutura de dados, usar um var imutável em vez de um val mutável tem a vantagem de vazar referências à estrutura sem arriscar que ela seja modificada por outros de uma forma que rompa suas suposições locais. A desvantagem para esses "outros" é que eles podem ler dados obsoletos.
Malte Schwerhoff
Mudei de ideia e concordo com você (estou deixando meu comentário original para a história). Eu usei isso desde então, especialmente em var list: List[X] = Nil; list = item :: list; ...e esqueci que uma vez escrevi de forma diferente.
Jim Pivarski
@MalteSchwerhoff: "dados obsoletos" são realmente desejáveis, dependendo de como você projetou seu programa, se a consistência for crucial; este é, por exemplo, um dos principais princípios básicos de como a simultaneidade funciona no Clojure.
Erik Kaplun
@ErikAllik Eu não diria que dados desatualizados são desejáveis ​​em si, mas concordo que podem estar perfeitamente bem, dependendo das garantias que você deseja / precisa dar aos seus clientes. Ou você tem um exemplo em que o simples fato de ler dados desatualizados é realmente uma vantagem? Não me refiro às consequências de aceitar dados desatualizados, o que poderia ser melhor desempenho ou uma API mais simples.
Malte Schwerhoff
9

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 nosso varpor 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.

Jmazin
fonte
1

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

Bogdan Nicolau
fonte
-2

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.

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

Resultado:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

Algo assim não pode acontecer com var immutable, porque a reatribuição não é permitida:

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

Resulta em:

error: reassignment to val

Uma vez que os parâmetros de função são tratados como valessa reatribuição não é permitida.

Akavall
fonte
Isso está incorreto. O motivo pelo qual você obteve esse erro é porque você usou Vector em seu segundo exemplo que, por padrão, é imutável. Se você usar um ArrayBuffer, verá que ele compila bem e faz a mesma coisa onde apenas adiciona o novo elemento e imprime o buffer mutado. pastebin.com/vfq7ytaD
EdgeCaseBerg
@EdgeCaseBerg, estou usando intencionalmente um Vector no meu segundo exemplo, porque estou tentando mostrar que o comportamento do primeiro exemplo mutable valnão é possível com o immutable var. O que aqui está incorreto?
Akavall
Você está comparando maçãs com laranjas aqui. Vector não tem +=método como o buffer de matriz. Suas respostas indicam que +=é o mesmo x = x + yque 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.
EdgeCaseBerg
@EdgeCaseBerg Mas você não pode replicar o comportamento que obtenho ArrayBufferusando Vector. 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 ser valnão ajuda); immutable varé mais seguro do que mutable val.
Akavall