56

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.

woky
  • 4,686
  • 9
  • 36
  • 44
  • 9
    After the clarification the answer is: You can’t. – Debilski Mar 02 '12 at 15:49
  • 3
    That's what I suspected. I'm going to post it to scala-debate. – woky Mar 02 '12 at 16:04
  • 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 Mar 02 '12 at 16:31
  • 4
    @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 Mar 02 '12 at 16:51
  • @DanielC.Sobral Derp. I stand corrected. – Destin Mar 02 '12 at 17:28
  • @Destin: I don't buy it. If vars are allowed in method body, there's no reason they shouldn't be allowed in parameters. For me Scala is more like Python or Perl where I can choose wheter I'll do functional or imperative style. – woky Mar 02 '12 at 18:39
  • @woky And no one is taking that choice away from you; you're just very mildly inconvenienced by needing to place the parameter into a local variable. Scala is simply _encouraging_ you to do the more-functional thing. You're certainly free to continue taking this up with the rest of the community–whom I surely do not speak for–but I simply informing you of what to expect. – Destin Mar 02 '12 at 18:46
  • @Destin I agree with woky and will proceed to scala-debate to support his thread (if I can find it). It makes no sense to force the verbosity, because overwriting the value in the input parameter does not make the function non-pure. Only modifying the data contained in that value that the parameter references would make the function non-pure, and that applies to the val on the members of the class for the data. This is a language design error. – Shelby Moore III Sep 08 '13 at 18:40
  • I added a discussion thread, "function parameters are not allowed to be `var`", at scala-debate since I couldn't find yours. – Shelby Moore III Sep 08 '13 at 19:36
  • Scala specifically positions itself as a functional language. As such, it would be logical to infer it makes no promises about allowing a developer to use imperative paradigms. As a heavy Python user I'm used to the reverse. Many people in that community want more functional features, but Guido (creator and BDFL) has stated on many occasions Python is not a functional language, it simply has some functions that people associate with functional languages because they're really useful. – Endophage Nov 07 '14 at 07:02
  • 1
    If you are thinking, "I wish my programming language could mutate parameters", then the problem is not the programming language. – Tim Harper Jan 24 '15 at 20:29
  • For the record, the fact that you do not need this anymore is irrelevant. This is a very good question for those of us who know that being able to re-purpose an identifier is much better than being forced to have the original useless identifier in scope, and running the danger of accidentally referring to it, thus introducing a subtle bug. The fact that Scala does not allow modifying method arguments is a prime example of what I would call functional nazism. – Mike Nakis Jan 23 '19 at 16:08
  • I upvoted the question because it is a very good one, and one I had just asked myself. Getting a straight answer "You can't" is also helpful :-) – Roland Weber Apr 01 '19 at 11:56

7 Answers7

38

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 
}
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • 1
    For Scala newbies: if you're declaring a `class Constructor`, then you can choose the members to be `val` or `var`. e.g. `class Person(val name: String, var age: Int)` – Ricardo Aug 25 '22 at 19:23
10

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.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 2
    Hello. Thank you. I'm afraid you misunderstood my question. I've updated it. – woky Mar 02 '12 at 15:44
  • 3
    "The JVM does not allow pass-by-reference of pointers to objects" This point requires some very careful clarification. When passing a primitive in Java: we are indeed passing by value. But, when passing an object: you are in-fact passing a reference to that object (which we can claim is also being done: by-value). For this reason, the "Java only passes by value" statement actually causes a lot of confusion but really that assertion requires the understanding that primitives are passed by value and object *references* are passed by value. – Ryan Delucchi Apr 02 '13 at 18:29
7

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.

Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
1

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
    //
}
Giorgio
  • 5,023
  • 6
  • 41
  • 71
  • 1
    Hello. Thank you. I'm afraid you misunderstood my question. I've updated it. – woky Mar 02 '12 at 15:44
  • Ad 2. Not an option, that's just clutter. StringBuilder implies I'll be building some big string. Why doing "String -> StringBuilder -> String" if I can do "String (-> maybe new String)"? Your solution is just a workaround to either lack of knowledge of Scala or limitation of Scala. You wouldn't do this in Java, would you? – woky Mar 02 '12 at 15:55
  • I did not say that using a StringBuilder is elegant, but if you do not want to return a string as a return value then you have to use an argument of some mutable string type. Actually you can use endsWith() directly on a StringBuilder in Scala (you do not need to call toString()). – Giorgio Mar 02 '12 at 16:04
  • 1
    Regarding the original question: Scala encourages functional programming, so even using ordinary var's (local variables) should be avoided when possible. – Giorgio Mar 02 '12 at 16:08
  • Giorgio: I don't think so. Perl supports closures for a very long time and it doesn't encourage you to do functional programming. Scala is similar. This whole "you do it wrong, do it functionally" is just hype around Scala. I like Scala because I can choose. – woky Mar 02 '12 at 18:46
  • 1
    @woky: From Odersky (Scala's inventor) et al.: Programming in Scala, 2nd Edition, page 52: "Scala allows you to program in an imperative style, but encourages you to adopt a more functional style" and later "Scala encourages you to lean towards vals, but ultimately reach for the best tool given the job at hand." So, according to its inventor, Scala does encourage to do functional programming, even though it does not force you to. Also, how are closures in Perl related to this? – Giorgio Mar 02 '12 at 19:43
0

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)
}
Sledge
  • 178
  • 13
0

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))
Dylan
  • 13,645
  • 3
  • 40
  • 67
0

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.

Dave Griffith
  • 20,435
  • 3
  • 55
  • 76