Reputation: 4936
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.
Upvotes: 58
Views: 48827
Reputation: 178
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)
}
Upvotes: 0
Reputation: 59994
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
}
Upvotes: 39
Reputation: 51109
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.
Upvotes: 7
Reputation: 20515
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.
Upvotes: 0
Reputation: 167891
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.
Upvotes: 10
Reputation: 13914
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))
Upvotes: 0
Reputation: 5173
String objects are immutable in Scala (and Java). The alternatives I can think of are:
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
//
}
Upvotes: 1