Qual é a diferença entre uma definição var e val no Scala?

301

Qual é a diferença entre a vare valdefinition em Scala e por que o idioma precisa de ambos? Por que você escolheria um valover a vare vice-versa?

Derek Mahar
fonte

Respostas:

331

Como muitos outros disseram, o objeto atribuído a uma valnão pode ser substituído e o objeto atribuído a uma varlata. No entanto, o referido objeto pode ter seu estado interno modificado. Por exemplo:

class A(n: Int) {
  var value = n
}

class B(n: Int) {
  val value = new A(n)
}

object Test {
  def main(args: Array[String]) {
    val x = new B(5)
    x = new B(6) // Doesn't work, because I can't replace the object created on the line above with this new one.
    x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
    x.value.value = 6 // Works, because A.value can receive a new object.
  }
}

Portanto, mesmo que não possamos alterar o objeto atribuído x, poderíamos alterar o estado desse objeto. No fundo, no entanto, havia umvar .

Agora, a imutabilidade é uma coisa boa por vários motivos. Primeiro, se um objeto não mudar de estado interno, você não precisa se preocupar se alguma outra parte do seu código está mudando. Por exemplo:

x = new B(0)
f(x)
if (x.value.value == 0)
  println("f didn't do anything to x")
else
  println("f did something to x")

Isso se torna particularmente importante nos sistemas multithread. Em um sistema multithread, o seguinte pode acontecer:

x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

Se você usar valexclusivamente e usar apenas estruturas de dados imutáveis ​​(ou seja, evitar matrizes, tudo emscala.collection.mutable etc.), tenha certeza de que isso não acontecerá. Ou seja, a menos que haja algum código, talvez até uma estrutura, fazendo truques de reflexão - a reflexão pode alterar valores "imutáveis", infelizmente.

Essa é uma razão, mas há outra razão para isso. Quando você usa var, pode ser tentado a reutilizar o mesmo varpara várias finalidades. Isso tem alguns problemas:

  • Será mais difícil para as pessoas que leem o código saber qual é o valor de uma variável em uma determinada parte do código.
  • Você pode esquecer de reinicializar a variável em algum caminho do código e acabar passando valores incorretos a jusante no código.

Simplificando, o uso valé mais seguro e leva a um código mais legível.

Podemos então ir na outra direção. Se valisso é melhor, por que var? Bem, algumas línguas seguiram esse caminho, mas há situações em que a mutabilidade melhora muito o desempenho.

Por exemplo, tome uma imutável Queue. Quando você enqueueou algo dequeuenele, você recebe uma novaQueue objeto. Como, então, você processaria todos os itens nele?

Vou passar por isso com um exemplo. Digamos que você tenha uma fila de dígitos e deseja compor um número a partir deles. Por exemplo, se eu tiver uma fila com 2, 1, 3, nessa ordem, desejo recuperar o número 213. Vamos primeiro resolvê-lo com um mutable.Queue:

def toNum(q: scala.collection.mutable.Queue[Int]) = {
  var num = 0
  while (!q.isEmpty) {
    num *= 10
    num += q.dequeue
  }
  num
}

Este código é rápido e fácil de entender. Sua principal desvantagem é que a fila que é passada é modificada toNum, portanto, você deve fazer uma cópia dela com antecedência. Esse é o tipo de gerenciamento de objetos que a imutabilidade liberta.

Agora, vamos transformá-lo em um immutable.Queue:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
    if (qr.isEmpty)
      num
    else {
      val (digit, newQ) = qr.dequeue
      recurse(newQ, num * 10 + digit)
    }
  }
  recurse(q, 0)
}

Porque não consigo reutilizar alguma variável para acompanhar minha num , como no exemplo anterior, preciso recorrer à recursão. Nesse caso, é uma recursão de cauda, ​​que tem um desempenho muito bom. Mas nem sempre é esse o caso: às vezes não há apenas uma solução boa (legível, simples) de recursão da cauda.

Note, no entanto, que posso reescrever esse código para usar um immutable.Queuee um varao mesmo tempo! Por exemplo:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  var qr = q
  var num = 0
  while (!qr.isEmpty) {
    val (digit, newQ) = qr.dequeue
    num *= 10
    num += digit
    qr = newQ
  }
  num
}

Esse código ainda é eficiente, não requer recursão e você não precisa se preocupar se precisa fazer uma cópia da sua fila ou não antes de ligar toNum. Naturalmente, evitei reutilizar variáveis ​​para outros fins, e nenhum código fora dessa função as vê; portanto, não preciso me preocupar com a alteração de seus valores de uma linha para a próxima - exceto quando explicitamente o faço.

A Scala optou por deixar o programador fazer isso, se o programador considerasse a melhor solução. Outros idiomas optaram por dificultar esse código. O preço que Scala (e qualquer idioma com grande mutabilidade) paga é que o compilador não tem tanta margem de manobra para otimizar o código quanto poderia. A resposta de Java para isso é otimizar o código com base no perfil de tempo de execução. Poderíamos continuar falando sobre prós e contras de cada lado.

Pessoalmente, acho que o Scala encontra o equilíbrio certo, por enquanto. Não é perfeito, de longe. Acho que Clojure e Haskell têm noções muito interessantes não adotadas por Scala, mas Scala também tem suas próprias forças. Vamos ver o que acontece no futuro.

Daniel C. Sobral
fonte
Um pouco tarde, mas ... Faz var qr = quma cópia q?
5
@davips Não faz uma cópia do objeto referenciado por q. Ele faz uma cópia - na pilha, não na pilha - da referência a esse objeto. Quanto ao desempenho, você terá que ser mais claro sobre o que está falando.
Daniel C. Sobral
Ok, com a sua ajuda e algumas informações ( (x::xs).drop(1)é exatamente xs, e não uma "cópia" de xs) a partir daqui ligar eu poderia entender. tnx!
"Este código ainda é eficiente" - é? Como qré uma fila imutável, toda vez que a expressão qr.dequeueé chamada, ela cria um new Queue(consulte < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ).
Owen
@ Owen Sim, mas observe que é um objeto raso. O código ainda é O (n) mutável, se você copiar a fila ou imutável.
Daniel C. Sobral
61

valé final, ou seja, não pode ser definido. Pense finalem java.

Jackson Davis
fonte
3
Mas se eu entendi corretamente (não um especialista em Scala), as val variáveis são imutáveis, mas os objetos a que se referem não precisam ser. De acordo com o link que Stefan postou: "Aqui, a referência de nomes não pode ser alterada para apontar para uma matriz diferente, mas a própria matriz pode ser modificada. Em outras palavras, o conteúdo / elementos da matriz podem ser modificados." Então é como finalfunciona em Java.
Daniel Pryden
3
Exatamente por que eu postei como está. Eu posso chamar +=em um hashmap mutabled definido como um valbem-acredito seu exatamente como finalobras em java
Jackson Davis
Ack, eu pensei que os tipos de scala embutidos poderiam fazer melhor do que simplesmente permitir a realocação. Eu preciso checar os fatos.
27613 Stefan Kendall
Eu estava confundindo os tipos imutáveis ​​de Sequência de scala com a noção geral. A programação funcional me deu toda a volta.
27413 Stefan Kendall
Eu adicionei e removi um personagem falso na sua resposta para que eu pudesse lhe dar o voto positivo.
27413 Stefan Kendall
56

Em termos simples:

var = var iable

val = v ariável + final al

Ajay Gupta
fonte
20

A diferença é que a varpode ser reatribuído a enquanto que a valnão pode. A mutabilidade, ou não, do que é realmente atribuído, é uma questão secundária:

import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.

Enquanto que:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

E, portanto:

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

Se você estiver construindo uma estrutura de dados e todos os seus campos forem vals, essa estrutura de dados será imutável, pois seu estado não pode ser alterado.

oxbow_lakes
fonte
1
Isso só é verdade se as classes desses campos também forem imutáveis.
229 Daniel Daniel Sobral
Sim - eu ia colocar isso, mas pensei que poderia ser um passo longe demais! É também um ponto discutível, eu diria; de uma perspectiva (embora não funcional), seu estado não muda, mesmo que o estado de seu estado mude.
Oxbow_lakes
Por que ainda é tão difícil criar um objeto imutável em uma linguagem JVM? Além disso, por que o Scala não tornou os objetos imutáveis ​​por padrão?
Derek Mahar
19

valsignifica imutável e varsignifica mutável.

Discussão completa.

Stefan Kendall
fonte
1
Isto simplesmente não é verdade. O artigo vinculado fornece uma matriz mutável e a chama imutável. Nenhuma fonte séria.
Klaus Schulz
Na verdade não é verdade. Tente esse val b = Matriz [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println (""))
Guillaume
13

Pensando em termos de C ++,

val x: T

é análogo ao ponteiro constante para dados não constantes

T* const x;

enquanto

var x: T 

é análogo a ponteiro não constante a dados não constantes

T* x;

Favorecendo valmaisvar aumenta a imutabilidade da base de código, o que pode facilitar sua correção, simultaneidade e compreensibilidade.

Para entender o significado de ter um ponteiro constante para dados não constantes, considere o seguinte snippet do Scala:

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

Aqui o "ponteiro" val m é constante, portanto não podemos atribuí-lo novamente para apontar para algo mais ou menos assim

m = n // error: reassignment to val

no entanto, podemos realmente alterar os dados não constantes em si que mapontam para gostar

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
Mario Galic
fonte
1
Acho que Scala não levou a imutabilidade à sua conclusão final: ponteiro constante e dados constantes. Scala perdeu uma oportunidade de tornar objetos imutáveis ​​por padrão. Consequentemente, Scala não tem a mesma noção de valor que Haskell.
Derek Mahar
@DerekMahar, você está certo, mas um objeto pode se apresentar perfeitamente imutável, enquanto ainda usa mutabilidade em sua implementação, por exemplo , por razões de desempenho. Como o compilador seria capaz de separar entre mutabilidade verdadeira e mutabilidade somente interna?
francoisr
8

"val significa imutável e var significa mutável."

Parafraseando, "val significa valor e var significa variável".

Uma distinção que é extremamente importante na computação (porque esses dois conceitos definem a essência do que é a programação) e que o OO conseguiu desfocar quase completamente, porque no OO, o único axioma é que "tudo é um objeto". E que, como conseqüência, muitos programadores hoje em dia tendem a não entender / apreciar / reconhecer, porque foram submetidos a uma lavagem cerebral em "pensar da maneira OO" exclusivamente. Muitas vezes, levando a que objetos variáveis ​​/ mutáveis ​​sejam usados ​​como em qualquer lugar , quando objetos de valor / imutáveis ​​podem / teriam sido melhores.

Erwin Smout
fonte
Essa é a razão pela qual prefiro o Haskell ao invés do Java, por exemplo.
Derek Mahar
6

val significa imutável e var significa mutável

você pode pensar valcomo o finalmundo chave da linguagem de programação java ou o mundo chave da linguagem c ++ const

Rollen Holt
fonte
1

Valsignifica sua final , não pode ser reatribuída

Visto que Varpode ser reatribuído mais tarde .

Crime_Master_GoGo
fonte
1
Qual é a diferença desta resposta entre as 12 respostas já enviadas?
jwvh
0

É tão simples quanto o nome.

var significa que pode variar

val significa invariável

user105003
fonte
0

Val - values ​​são constantes de armazenamento digitadas. Uma vez criado, seu valor não pode ser reatribuído. um novo valor pode ser definido com a palavra-chave val.

por exemplo. val x: Int = 5

Aqui, o tipo é opcional, pois o scala pode inferir a partir do valor atribuído.

Variáveis ​​são unidades de armazenamento digitadas que podem receber valores novamente, desde que o espaço de memória seja reservado.

por exemplo. var x: Int = 5

Os dados armazenados nas duas unidades de armazenamento são automaticamente desalocados pela JVM, uma vez que não são mais necessários.

Em scala, os valores são preferidos às variáveis ​​devido à estabilidade que elas trazem ao código, particularmente no código simultâneo e multithread.

SunTech
fonte
0

Embora muitos já tenham respondido à diferença entre Val e var . Mas um ponto a ser observado é que val não é exatamente como a palavra-chave final .

Podemos alterar o valor de val usando recursão, mas nunca podemos alterar o valor de final. Final é mais constante que Val.

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

Os parâmetros do método são, por padrão, val e a cada valor de chamada está sendo alterado.

Mahesh Chand
fonte