web-dev-qa-db-fra.com

Scala - Référence de paramètre de la méthode mutable (var)

EDIT: Je continue à recevoir des votes ici. Pour mémoire, je ne pense plus que ce soit important. Je n'en ai pas eu besoin depuis que je l'ai posté.

J'aimerais faire la suite à Scala ...

def save(srcPath: String, destPath: String) {
    if (!destPath.endsWith('/'))
        destPath += '/'
    // do something
}

... mais je ne peux pas parce que destPath est un val. Est-il possible de déclarer destPath en tant que var?

Remarque: il y a des questions similaires mais dans chacune d'elles, OP voulait seulement modifier un tableau.

Veuillez ne pas conseiller de suivre:

La mutation des paramètres d'entrée est souvent considérée comme un mauvais style et le rend plus difficile de raisonner sur le code.

Je pense que c'est valable dans la programmation impérative (Scala permet les deux, n'est-ce pas?) Et d'ajouter quelque chose comme tmpDestPath ne ferait qu'ajouter du fouillis.

EDIT: Ne vous méprenez pas. Je sais que les chaînes ne sont pas mutables et je ne veux pas de référence à référence car je ne veux pas modifier les données de l'appelant. Je veux juste modifier la référence locale à la chaîne que l'appelant m'a donnée avec ma chaîne (par exemple, orig + '/'). Je veux modifier cette valeur uniquement dans le cadre de la méthode actuelle. Regardez, ceci est parfaitement valable en Java:

void printPlusOne(int i) {
    i++;
    System.out.println("i is: " + i);
    System.out.println("and now it's same: " + i);
}

Je n'ai pas à créer de nouvelle variable et je n'ai pas à calculer i + 1 deux fois.

36
woky

Tu ne peux pas.

Vous devrez déclarer une var supplémentaire (ou utiliser un style plus fonctionnel :-)).

Exemple simpliste:

def save(srcPath: String, destPath: String) {
    val normalizedDestPath =
      if (destPath.endsWith('/')) destPath
      else destPath + '/'
    // do something with normalizedDestPath 
}
27

La machine virtuelle Java n'autorise pas le passage par référence de pointeurs sur des objets (comme vous le feriez en C++), de sorte que vous ne pouvez pas faire exactement ce que vous voulez.

Une option consiste à renvoyer la nouvelle valeur:

def save(srcPath: String, destPath: String): String = {
  val newPath = (if (!destPath.endsWith("/")) destPath+'/' else destPath)
  // do something
  newPath
}

Une autre consiste à créer un wrapper:

case class Mut[A](var value: A) {}

def save(srcPath: String, destPath: Mut[String]) {
  if (!destPath.value.endsWith("/")) destPath.value += '/'
  // do something
}

que les utilisateurs devront ensuite utiliser en entrant. (Bien sûr, ils seront tentés de save("/here",Mut("/there")) qui jettera les modifications, mais c'est toujours le cas des arguments de fonction de référence par référence.)


Edit: ce que vous proposez est l’une des plus grandes sources de confusion parmi les programmeurs non experts. En d’autres termes, lorsque vous modifiez l’argument d’une fonction, modifiez-vous une copie locale (définition par valeur) ou l’original (définition par référence)? Si vous ne pouvez même pas le modifier}, il est assez clair que tout ce que vous faites est une copie locale.

Faites-le simplement comme ça.

val destWithSlash = destPath + (if (!destPath.endsWith("/")) "/" else "")

Cela vaut le coup de l’absence de confusion quant à ce qui se passe réellement.

9
Rex Kerr

Peut-être que vous pourriez obtenir le système de typographie pour faire le travail à votre place, de sorte que vous n'avez même pas à vous soucier d'ajouter une barre oblique à chaque fois:

class SlashString(s: String) {
  override val toString = if (s endsWith "/") s else s + "/"
}
implicit def toSlashString(s: String) = new SlashString(s)

Maintenant, vous n'avez plus besoin de code pour changer l'entrée String:

def save(srcPath: String, destPath: SlashString) {
  printf("saving from %s to %s", srcPath, destPath)
}

val src: String = "abc"
val dst: String = "xyz"

scala> save(src, dst)
saving from abc to xyz/

Certes, il y a un peu de configuration au début, mais ce sera moins le cas avec les classes implicites dans la version 2.10, et cela enlèvera tout l'encombrement de la méthode, ce qui vous inquiétait.

5
Luigi Plinge

Les objets string sont immuables dans Scala (et Java). Les alternatives que je peux penser sont:

  1. Renvoie la chaîne de résultat en tant que valeur de retour.
  2. Au lieu d'utiliser un paramètre String, utilisez un StringBuffer ou StringBuilder, qui ne sont pas immuables.

Dans le second scénario, vous auriez quelque chose comme:

def save(srcPath: String, destPath: StringBuilder) {
    if (!destPath.toString().endsWith("/"))
       destPath.append("/")
    // do something
    //
}

MODIFIER

Si je comprends bien, vous voulez utiliser l'argument comme une variable locale. Vous ne pouvez pas, car tous les arguments de méthodes sont vals dans Scala . La seule chose à faire est de le copier d'abord dans une variable locale:

def save(srcPath: String, destPath: String) {
    var destP = destPath
    if (!destP.endsWith("/"))
       destP += "/"
    // do something
    //
}
1
Giorgio

Je sais que c’est une vieille question, mais si vous voulez simplement réutiliser le nom de l’argument, par exemple:

def save(srcPath: String, destPath: String) {
  ((destPath: String) => {
    // do something
  })(if (!destPath.endsWith('/')) destPath + '/' else destPath)
}
0
Sledge

Non, ce n'est pas permis à Scala. D'autres ont décrit certaines solutions de contournement de bas niveau (toutes bonnes), mais je vais en ajouter une de niveau supérieur. Pour ce genre de normalisation de chaîne, je conserve une extension pimped à scala.String avec des méthodes comme suffixe, préfixe, removeSuffix et removePrefix. le suffixe et le préfixe ajoutent ou préfixent une chaîne sur une autre, à moins que le suffixe ou le préfixe ne soit déjà là. removeSuffix et removePrefix procèdent de manière évidente, en supprimant une chaîne de la fin ou du début d'une autre, si elle est présente. Votre cas d'utilisation serait écrit

val normalizedPath = destPath.addSuffix("/") 

Si vous effectuez une série d'analyses de données ou d'opérations sur les fichiers, ces méthodes sont si pratiques que vous ne croirez pas que vous les avez déjà utilisées sans elles.

0
Dave Griffith

Voici quelques suggestions:

1) Mettez à jour votre fonction un peu

def save(srcPath: String, destPath: String) {
  var dp = destPath
  if (!dp.endsWith('/'))
    dp+= '/'
  // do something, but with dp instead of destPath
}

2) Créer une fonction utilitaire à utiliser avant d'appeler save

def savedPath(path: String) = 
  if(path.endsWith("/")) 
    path
  else
    path + "/"

//call your save method on some path
val myDestPath = ...
val srcPath = ...
save(srcPath, savedPath(myDestPath))
0
Dylan