Scala - mutable (var) method parameter reference

ScalaVariablesMethodsParameters

Scala Problem Overview


EDIT: I keep getting upvotes here. Just for the record, I no longer think this is important. I haven't needed it since I posted it.

I would like to do following in Scala ...

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

... but I can't beacuse destPath is a val. Is there any way to declare destPath as var?

Note: there are similar questions but in all of them OP just wanted to modify array.

Please do not advise following:

> Mutating the input parameters is often seen as bad style and makes it > harder to reason about code.

I think it's valid in imperative programming (Scala allows both, right?) and adding something like tmpDestPath would just add clutter.

EDIT: Don't misunderstand. I know that strings aren't mutable and I don't want a reference to reference because I don't want to modify data of caller. I just want to modify local reference to string that caller gave me with my string (eg. orig + '/'). I want to modify that value only in scope of current method. Look, this is perfectly valid in Java:

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

I don't have to create new variable and i don't have to compute i+1 twice.

Scala Solutions


Solution 1 - Scala

You can't.

You'll have to declare an extra var (or use a more functional style :-)).

Simplistic example:

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

Solution 2 - Scala

The JVM does not allow pass-by-reference of pointers to objects (which is how you'd do this in C++), so you can't do exactly what you want.

One option is to return the new value:

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

Another is to create a wrapper:

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

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

which users will then have to use on the way in. (Of course, they'll be tempted to save("/here",Mut("/there")) which will throw away the alterations, but this is always the case with pass-by-reference function arguments.)


Edit: what you're proposing is one of the biggest sources of confusion among non-expert programmers. That is, when you modify the argument of a function, are you modifying a local copy (pass-by-value) or the original (pass-by-reference)? If you cannot even modify it it is pretty clear that anything you do is a local copy.

Just do it that way.

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

It's worth the lack of confusion about what is actually going on.

Solution 3 - Scala

Maybe you could get the type system to do the work for you, so you don't even need to worry about adding a slash each time:

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

Now you don't need any code at all to change the input 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/

True, there's a bit of setup at the start, but this will be less-so with implicit classes in version 2.10, and it removes all clutter from the method, which was what you were worried about.

Solution 4 - Scala

String objects are immutable in Scala (and Java). The alternatives I can think of are:

  1. Return the result string as return value.
  2. Instead of using a String parameter, use a StringBuffer or StringBuilder, which are not immutable.

In the second scenario you would have something like:

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

EDIT

If I understand correctly, you want to use the argument as a local variable. You can't, because all method arguments are val's in Scala. The only thing to do is to copy it to a local variable first:

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

Solution 5 - Scala

Here's a couple of suggestions:

  1. Update your function a bit

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

  2. Create a utility function to use before calling 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))

Solution 6 - Scala

No, that's not allowed in Scala. Others have described some low-level workarounds (all good), but I'll add a higher-level one. For just this sort of string normalization purposes, I keep around a pimped extension to scala.String with methods like suffix, prefix, removeSuffix, and removePrefix. suffix and prefix append or prepend one string onto another, unless the suffix or prefix is already there. removeSuffix and removePrefix do the obvious, removing one string from the end or beginning of another, if it's present. Your use case would be written

val normalizedPath = destPath.addSuffix("/") 

If you do a bunch of data analysis or file operations, these methods are so handy that you won't believe that you ever did without them.

Solution 7 - Scala

I know this is an old question, but if you just want to reuse the argument name perhaps:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwokyView Question on Stackoverflow
Solution 1 - ScalaJean-Philippe PelletView Answer on Stackoverflow
Solution 2 - ScalaRex KerrView Answer on Stackoverflow
Solution 3 - ScalaLuigi PlingeView Answer on Stackoverflow
Solution 4 - ScalaGiorgioView Answer on Stackoverflow
Solution 5 - ScalaDylanView Answer on Stackoverflow
Solution 6 - ScalaDave GriffithView Answer on Stackoverflow
Solution 7 - ScalaSledgeView Answer on Stackoverflow