50
votes

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.

7
After the clarification the answer is: You can’t.Debilski
That's what I suspected. I'm going to post it to scala-debate.woky
Well, the Scala community is not really going to be in favor of being able to directly modify function parameters, whether by value or by reference. The reasoning is the same as that of why Scala also lacks something else from your example: the unary ++ operator(s) for numerical types. Such things reek of a non-functional, side-effect-oriented programming style, which is something that Scala generally encourages you to avoid. As it stands, if you want to repeatedly mutate a function parameter, you must first store it into a var, which makes your intentions clearer, anyway!Destin
@Destin Actually, that's not the reason at all for the lack of ++. The problem with ++ is that it cannot be implemented as a method of a class -- it would have to be a language feature built in the compiler, and specific to certain types.Daniel C. Sobral
If you are thinking, "I wish my programming language could mutate parameters", then the problem is not the programming language.Tim Harper

7 Answers

34
votes

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 
}
10
votes

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.

7
votes

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.

1
votes

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
    //
}
0
votes

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))
0
votes

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.

0
votes

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