Quelle est la différence entre une définition var et val dans Scala?

301

Quelle est la différence entre a varet valdéfinition en Scala et pourquoi la langue a-t-elle besoin des deux? Pourquoi choisiriez-vous un valsur un varet vice versa?

Derek Mahar
la source

Réponses:

331

Comme beaucoup d'autres l'ont dit, l'objet affecté à un valne peut pas être remplacé et l'objet affecté à une varboîte. Cependant, ledit objet peut voir son état interne modifié. Par exemple:

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.
  }
}

Donc, même si nous ne pouvons pas changer l'objet assigné x, nous pouvons changer l'état de cet objet. À l'origine, cependant, il y avait un var.

Maintenant, l'immuabilité est une bonne chose pour de nombreuses raisons. Tout d'abord, si un objet ne change pas d'état interne, vous n'avez pas à vous inquiéter si une autre partie de votre code le change. Par exemple:

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")

Cela devient particulièrement important avec les systèmes multithread. Dans un système multithread, les événements suivants peuvent se produire:

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

Si vous utilisez valexclusivement et n'utilisez que des structures de données immuables (c'est-à-dire, évitez les tableaux, tout ce qui s'y trouve scala.collection.mutable, etc.), vous pouvez être assuré que cela ne se produira pas. Autrement dit, à moins qu'il y ait du code, peut-être même un cadre, faisant des tours de réflexion - la réflexion peut malheureusement changer les valeurs "immuables".

C'est une raison, mais il y en a une autre. Lorsque vous utilisez var, vous pouvez être tenté de réutiliser le même varà plusieurs fins. Cela a quelques problèmes:

  • Il sera plus difficile pour les personnes lisant le code de savoir quelle est la valeur d'une variable dans une certaine partie du code.
  • Vous pouvez oublier de réinitialiser la variable dans un chemin de code et finir par transmettre des valeurs incorrectes en aval dans le code.

Autrement dit, l'utilisation valest plus sûre et conduit à un code plus lisible.

On peut alors aller dans l'autre sens. Si valc'est mieux, pourquoi en avoir var? Eh bien, certains langages ont choisi cette voie, mais il existe des situations dans lesquelles la mutabilité améliore considérablement les performances.

Par exemple, prenez un immuable Queue. Lorsque vous enqueueou des dequeueobjets, vous obtenez un nouvel Queueobjet. Comment allez-vous alors traiter tous les éléments qu'il contient?

Je vais passer par là avec un exemple. Disons que vous avez une file d'attente de chiffres et que vous souhaitez en composer un nombre. Par exemple, si j'ai une file d'attente avec 2, 1, 3, dans cet ordre, je veux récupérer le nombre 213. Résolvons-le d'abord avec un mutable.Queue:

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

Ce code est rapide et facile à comprendre. Son principal inconvénient est que la file d'attente qui est passée est modifiée par toNum, vous devez donc en faire une copie au préalable. C'est le type de gestion d'objets dont l'immuabilité vous libère.

Maintenant, convertissons-le en un 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)
}

Parce que je ne peux pas réutiliser une variable pour garder une trace de ma num, comme dans l'exemple précédent, j'ai besoin de recourir à la récursivité. Dans ce cas, il s'agit d'une récursion de queue, qui a de très bonnes performances. Mais ce n'est pas toujours le cas: parfois, il n'y a tout simplement pas de bonne solution de récursion de queue (lisible, simple).

Notez cependant que je peux réécrire ce code pour utiliser un immutable.Queueet un varen même temps! Par exemple:

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
}

Ce code est toujours efficace, ne nécessite pas de récursivité et vous n'avez pas à vous soucier de savoir si vous devez faire une copie de votre file d'attente ou non avant d'appeler toNum. Naturellement, j'ai évité de réutiliser des variables à d'autres fins, et aucun code en dehors de cette fonction ne les voit, donc je n'ai pas besoin de m'inquiéter de voir leurs valeurs changer d'une ligne à l'autre - sauf lorsque je le fais explicitement.

Scala a choisi de laisser le programmeur faire cela, si le programmeur jugeait que c'était la meilleure solution. D'autres langues ont choisi de rendre ce code difficile. Le prix que Scala (et tout langage avec une mutabilité généralisée) paie est que le compilateur n'a pas autant de latitude pour optimiser le code qu'il pourrait autrement. La réponse de Java à cela est l'optimisation du code basé sur le profil d'exécution. Nous pourrions continuer indéfiniment sur les avantages et les inconvénients de chaque côté.

Personnellement, je pense que Scala trouve le bon équilibre, pour l'instant. Ce n'est pas parfait, de loin. Je pense que Clojure et Haskell ont des notions très intéressantes qui n'ont pas été adoptées par Scala, mais Scala a aussi ses propres forces. Nous verrons ce qui se prépare pour l'avenir.

Daniel C. Sobral
la source
Un peu tard, mais ... Fait-on var qr = qune copie de q?
5
@davips Il ne fait pas de copie de l'objet référencé par q. Il fait une copie - sur la pile, pas le tas - de la référence à cet objet. En ce qui concerne les performances, vous devrez être plus clair sur ce dont vous parlez.
Daniel C.Sobral
Ok, avec votre aide et quelques informations ( (x::xs).drop(1)c'est exactement xs, pas une "copie" de xs) ce lien que je pourrais comprendre. tnx!
"Ce code est toujours efficace" - n'est-ce pas? Étant donné qu'il qrs'agit d'une file d'attente immuable, chaque fois que l'expression qr.dequeueest appelée, elle crée un new Queue(voir < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ).
Owen
@Owen Oui, mais remarquez qu'il s'agit d'un objet peu profond. Le code est toujours O (n), qu'il soit modifiable, si vous copiez la file d'attente, ou immuable.
Daniel C.Sobral
61

valest définitive, c'est-à-dire qu'elle ne peut pas être définie. Pensez finalen java.

Jackson Davis
la source
3
Mais si je comprends bien (pas un expert Scala), les val variables sont immuables, mais les objets qu'elles référencent ne doivent pas nécessairement l'être. Selon le lien publié par Stefan: "Ici, la référence des noms ne peut pas être modifiée pour pointer vers un tableau différent, mais le tableau lui-même peut être modifié. En d'autres termes, le contenu / les éléments du tableau peuvent être modifiés." C'est donc comme cela finalfonctionne en Java.
Daniel Pryden
3
Exactement pourquoi je l'ai posté tel quel. Je peux faire appel +=à une table de hachage mutable définie comme valtrès bien - je crois que c'est exactement comment cela finalfonctionne en java
Jackson Davis
Ack, je pensais que les types de scala intégrés pourraient faire mieux que de simplement autoriser la réaffectation. Je dois vérifier les faits.
Stefan Kendall
Je confondais les types de séquences immuables de scala avec la notion générale. La programmation fonctionnelle m'a tout changé.
Stefan Kendall
J'ai ajouté et supprimé un caractère fictif dans votre réponse afin que je puisse vous donner le vote positif.
Stefan Kendall
56

En termes simples:

var = var iable

val = v ariable + fin al

Ajay Gupta
la source
20

La différence est que a varpeut être réaffecté à alors que valne peut pas. La mutabilité, ou autre de ce qui est réellement attribué, est un problème secondaire:

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.

Tandis que:

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

Et donc:

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

Si vous construisez une structure de données et que tous ses champs sont vals, cette structure de données est donc immuable, car son état ne peut pas changer.

oxbow_lakes
la source
1
Cela n'est vrai que si les classes de ces champs sont également immuables.
Daniel C.Sobral
Ouais - j'allais mettre ça dedans mais j'ai pensé que ça pourrait être un pas trop loin! C'est aussi un argument défendable, je dirais; d'un point de vue (même s'il n'est pas fonctionnel) son état ne change pas même si l'état de son état change.
oxbow_lakes
Pourquoi est-il encore si difficile de créer un objet immuable dans un langage JVM? De plus, pourquoi Scala n'a-t-il pas rendu les objets immuables par défaut?
Derek Mahar
19

valsignifie immuable et varsignifie mutable.

Discussion complète.

Stefan Kendall
la source
1
Ce n'est tout simplement pas vrai. L'article lié donne un tableau mutable et l'appelle immuable. Aucune source sérieuse.
Klaus Schulz
Pas vrai en effet. Essayez que val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Guillaume
13

Penser en termes de C ++,

val x: T

est analogue à un pointeur constant vers des données non constantes

T* const x;

tandis que

var x: T 

est analogue à un pointeur non constant vers des données non constantes

T* x;

privilégiant valsurvar augmente immuabilité de la base de code qui peut faciliter son exactitude, la simultanéité et compréhensible.

Pour comprendre la signification d'avoir un pointeur constant vers des données non constantes, considérez l'extrait de code Scala suivant:

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

Ici, le "pointeur" val m est constant, nous ne pouvons donc pas le réaffecter pour pointer vers autre chose comme ça

m = n // error: reassignment to val

mais nous pouvons en effet changer les données non constantes elles-mêmes qui mpointent comme si

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
Mario Galic
la source
1
Je pense que Scala n'a pas pris l'immuabilité à sa conclusion finale: pointeur constant et données constantes. Scala a raté l'occasion de rendre les objets immuables par défaut. Par conséquent, Scala n'a pas la même notion de valeur que Haskell.
Derek Mahar
@DerekMahar vous avez raison, mais un objet peut se présenter comme parfaitement immuable, tout en utilisant la mutabilité dans sa mise en œuvre, par exemple pour des raisons de performances. Comment le compilateur pourrait-il séparer la vraie mutabilité de la mutabilité interne uniquement?
francoisr
8

"val signifie immuable et var signifie mutable."

Pour paraphraser, "val signifie valeur et var signifie variable".

Une distinction qui s'avère extrêmement importante en informatique (car ces deux concepts définissent l'essence même de la programmation), et que OO a réussi à brouiller presque complètement, car en OO, le seul axiome est que "tout est un objet". Et qu'en conséquence, beaucoup de programmeurs de nos jours ont tendance à ne pas comprendre / apprécier / reconnaître, car ils ont été soumis à un lavage de cerveau pour "penser à la manière OO" exclusivement. Menant souvent à l'utilisation d'objets variables / mutables comme partout , alors que la valeur / les objets immuables auraient pu / auraient souvent été mieux.

Erwin Smout
la source
C'est la raison pour laquelle je préfère Haskell à Java, par exemple.
Derek Mahar
6

val signifie immuable et var signifie mutable

vous pouvez penser valcomme finalmonde clé du langage de programmation java ou monde clé du langage c ++ const

Rollen Holt
la source
1

Valsignifie sa finale , ne peut pas être réaffectée

Attendu que, Varpeut être réaffecté plus tard .

Crime_Master_GoGo
la source
1
En quoi cette réponse est-elle différente des 12 réponses déjà soumises?
jwvh
0

C'est aussi simple que son nom.

var signifie que cela peut varier

val signifie invariable

user105003
la source
0

Val - les valeurs sont des constantes de stockage typées. Une fois créée, sa valeur ne peut pas être réaffectée. une nouvelle valeur peut être définie avec le mot-clé val.

par exemple. val x: Int = 5

Ici, le type est facultatif car scala peut le déduire de la valeur affectée.

Var - les variables sont des unités de stockage typées auxquelles des valeurs peuvent être attribuées à nouveau tant que l'espace mémoire est réservé.

par exemple. var x: Int = 5

Les données stockées dans les deux unités de stockage sont automatiquement désallouées par JVM dès qu'elles ne sont plus nécessaires.

En scala, les valeurs sont préférées aux variables en raison de la stabilité qu'elles apportent au code, en particulier dans le code simultané et multithread.

SunTech
la source
0

Bien que beaucoup aient déjà répondu à la différence entre Val et var . Mais un point à noter est que val n'est pas exactement comme final mot-clé .

Nous pouvons changer la valeur de val en utilisant la récursivité mais nous ne pouvons jamais changer la valeur de final. La finale est plus constante que Val.

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

Les paramètres de méthode sont par défaut val et à chaque appel la valeur est modifiée.

Mahesh Chand
la source