Подтвердить что ты не робот

Scala - ссылка параметра метода mutable (var)

РЕДАКТИРОВАТЬ: Я продолжаю получать upvotes здесь. Для записи я больше не думаю, что это важно. Я не нуждался в нем, так как я разместил его.

Я хотел бы сделать следующее в Scala...

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

... но я не могу поверить, что destPath является val. Есть ли способ объявить destPath как var?

Примечание: есть похожие вопросы, но во всех них OP просто хотел изменить массив.

Пожалуйста, не сообщайте следующее:

Мутирование входных параметров часто рассматривается как плохой стиль и делает это сложнее рассуждать о коде.

Я думаю, что это действительно в императивном программировании (Scala позволяет оба, правильно?) и добавление чего-то вроде tmpDestPath просто добавит беспорядок.

РЕДАКТИРОВАТЬ: Не поймите неправильно. Я знаю, что строки не изменяются, и мне не нужна ссылка на ссылку, потому что я не хочу изменять данные вызывающего. Я просто хочу изменить локальную ссылку на строку, которую вызывающий мне дал с моей строкой (например, orig + '/'). Я хочу изменить это значение только в области текущего метода. Послушайте, это совершенно справедливо в Java:

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

Мне не нужно создавать новую переменную, и мне не нужно дважды вычислять я + 1.

4b9b3361

Ответ 1

Вы не можете.

Вам нужно объявить дополнительный var (или использовать более функциональный стиль: -)).

Упрощенный пример:

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

Ответ 2

JVM не позволяет передавать по ссылке указатели на объекты (как это делается на С++), поэтому вы не можете делать именно то, что хотите.

Один из вариантов - вернуть новое значение:

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

Другим является создание обертки:

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

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

которые затем будут использовать пользователи на пути. (Конечно, у них будет соблазн save("/here",Mut("/there")), который отбросит изменения, но это всегда происходит с аргументами аргументов pass-by-reference. )


Изменить: то, что вы предлагаете, является одним из самых больших источников путаницы среди не-экспертных программистов. То есть, когда вы изменяете аргумент функции, изменяете ли вы локальную копию (pass-by-value) или оригинал (pass-by-reference)? Если вы даже не можете его изменить, довольно ясно, что все, что вы делаете, это локальная копия.

Просто сделайте это.

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

Не стоит путаться в том, что происходит на самом деле.

Ответ 3

Возможно, вы могли бы получить систему типов, чтобы выполнить эту работу за вас, поэтому вам даже не нужно беспокоиться о добавлении косой черты каждый раз:

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

Теперь вам не нужен какой-либо код для изменения ввода 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/

Правда, вначале есть немного настроек, но это будет меньше, так как неявные классы в версии 2.10, и он удаляет весь беспорядок из метода, о чем вас беспокоило.

Ответ 4

Объекты String неизменяемы в Scala (и Java). Альтернативы, о которых я могу думать, следующие:

  • Вернуть строку результата в качестве возвращаемого значения.
  • Вместо использования параметра String используйте StringBuffer или StringBuilder, которые не являются неизменяемыми.

Во втором сценарии у вас будет что-то вроде:

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

ИЗМЕНИТЬ

Если я правильно понял, вы хотите использовать аргумент как локальную переменную. Вы не можете, потому что все аргументы метода - val в Scala. Единственное, что нужно сделать, это сначала скопировать его в локальную переменную:

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

Ответ 5

Вот несколько предложений:

1) Немного обновите свою функцию

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

2) Создайте функцию утилиты, которая будет использоваться перед вызовом 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))

Ответ 6

Нет, это не разрешено в Scala. Другие описали некоторые низкоуровневые обходные пути (все хорошо), но я добавлю более высокий уровень. Для такого рода нормализации строки я держу вокруг сутенерного расширения до Scala.String с помощью таких методов, как суффикс, префикс, removeSuffix и removePrefix. суффикс и префикс присоединяют или добавляют одну строку к другой, если суффикс или префикс уже есть. removeSuffix и removePrefix делают очевидное, удаляя одну строку с конца или начала другого, если она присутствует. Ваш прецедент будет написан

val normalizedPath = destPath.addSuffix("/") 

Если вы выполняете кучу анализа данных или операции с файлами, эти методы настолько удобны, что вы не поверите, что вы когда-либо делали без них.