Scala: Pourquoi mapValues ​​produit-il une vue et existe-t-il des alternatives stables?

Tout à l’heure, je suis surpris d’apprendre que mapValues produit une vue. La conséquence est illustrée dans l’exemple suivant:

 case class thing(id: Int) val rand = new java.util.Random val dissortingbution = Map(thing(0) -> 0.5, thing(1) -> 0.5) val perturbed = dissortingbution mapValues { _ + 0.1 * rand.nextGaussian } val sumProbs = perturbed.map{_._2}.sum val newDissortingbution = perturbed mapValues { _ / sumProbs } 

L’idée est que j’ai une dissortingbution qui est perturbée avec un certain hasard alors je la renormalise. Le code échoue en réalité dans son intention initiale: puisque mapValues produit une view , _ + 0.1 * rand.nextGaussian est toujours réévalué chaque fois que des perturbed sont utilisées.

Je suis en train de faire quelque chose comme la dissortingbution map { case (s, p) => (s, p + 0.1 * rand.nextGaussian) } , mais c’est juste un peu verbeux. Donc, le but de cette question est:

  1. Rappelez aux gens qui ne le savent pas.
  2. Recherchez les raisons pour lesquelles elles rendent les mapValues sortie de mapValues s.
  3. S’il existe une méthode alternative qui produit une Map concrète.
  4. Existe-t-il d’autres méthodes de collecte couramment utilisées qui ont ce piège?

Merci.

Il y a un billet à ce sujet, SI-4776 (par YT).

Le commit qui l’introduit a ceci à dire:

Suite à une suggestion de jrudolph, filterKeys et mapValues transformé des cartes abstraites et des fonctionnalités dupliquées pour des cartes immuables. transform et filterNot immuable aux cartes générales. Révision par phaller.

Je n’ai pas pu trouver la suggestion originale de jrudolph, mais je suppose que cela a été fait pour rendre mapValues plus efficace. Posez la question, cela peut être une surprise, mais mapValues est plus efficace si vous n’êtes pas susceptible de répéter plusieurs fois les valeurs.

En guise de mapValues(...).view.force , on peut faire mapValues(...).view.force pour produire une nouvelle Map .

Le scala doc dit:

une vue cartographique qui mappe chaque key de cette carte sur f(this(key)) . La carte résultante enveloppe la carte d’origine sans copier d’éléments.

Donc, cela devrait être prévu, mais cela me fait beaucoup peur, je vais devoir revoir des tas de codes demain. Je ne m’attendais pas à un tel comportement 🙁

Juste une autre solution de contournement:

Vous pouvez appeler toSeq pour obtenir une copie, et si vous en avez besoin de nouveau pour mapper sur toMap , mais cela inutile de créer des objects et d’avoir une implication de performance sur l’utilisation de map

On peut relativement facilement écrire, une mapValues qui ne crée pas de vue, je le ferai demain et posterai le code ici si personne ne le fait avant moi;)

MODIFIER:

J’ai trouvé un moyen simple de “forcer” la vue, utiliser “.map (identity)” après mapValues ​​(donc pas besoin d’implémenter une fonction spécifique):

 scala> val xs = Map("a" -> 1, "b" -> 2) xs: scala.collection.immutable.Map[java.lang.Ssortingng,Int] = Map(a -> 1, b -> 2) scala> val ys = xs.mapValues(_ + Random.nextInt).map(identity) ys: scala.collection.immutable.Map[java.lang.Ssortingng,Int] = Map(a -> 1315230132, b -> 1614948101) scala> ys res7: scala.collection.immutable.Map[java.lang.Ssortingng,Int] = Map(a -> 1315230132, b -> 1614948101) 

C’est dommage que le type retourné ne soit pas une vue! Sinon, on aurait pu appeler «force» …