【问题标题】:Scala - mutable (var) method parameter referenceScala - 可变(var)方法参数参考
【发布时间】:2012-03-21 02:31:35
【问题描述】:

编辑:我一直在这里获得支持。只是为了记录,我不再认为这很重要。自从我发布后我就不需要它了。

我想在 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's same: " + i);
}

我不必创建新变量,也不必计算 i+1 两次。

【问题讨论】:

  • 澄清后的答案是:不能。
  • 这就是我的怀疑。我要把它发布到 scala-debate 上。
  • 好吧,Scala 社区实际上并不赞成能够直接修改函数参数,无论是通过值还是通过引用。原因与为什么 Scala 还缺少您的示例中的其他内容的原因相同:用于数字类型的一元 ++ 运算符。这样的事情散发出一种非功能性、面向副作用的编程风格,这是 Scala 通常鼓励您避免的。就目前而言,如果你想重复改变一个函数参数,你必须先将它存储到一个var,这样你的意图就更清楚了,无论如何!
  • @Destin 实际上,这根本不是缺少++ 的原因。 ++ 的问题在于它不能作为类的方法来实现——它必须是编译器中内置的语言特性,并且特定于某些类型。
  • 如果你在想,“我希望我的编程语言可以改变参数”,那么问题不在于编程语言。

标签: scala variables methods parameters


【解决方案1】:

你不能。

您必须声明一个额外的var(或使用更实用的样式:-))。

简单的例子:

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

【讨论】:

    【解决方案2】:

    JVM 不允许通过引用传递指向对象的指针(这是您在 C++ 中执行此操作的方式),因此您无法完全按照您的意愿进行操作。

    一种选择是返回新值:

    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")),这将丢弃更改,但通过引用函数参数总是如此。 )


    编辑:您的提议是非专业程序员最大的困惑之一。也就是说,当您修改函数的参数时,您是在修改本地副本(按值传递)还是原始副本(按引用传递)?如果您甚至无法修改它,那么很明显,您所做的任何事情都是本地副本。

    就那样做吧。

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

    不必对实际发生的事情感到困惑。

    【讨论】:

    • 你好。谢谢你。恐怕你误解了我的问题。我已经更新了。
    • “JVM 不允许通过引用传递指向对象的指针” 这一点需要非常仔细的说明。在 Java 中传递原语时:我们确实是按值传递。但是,当传递一个对象时:您实际上是在传递对该对象的引用(我们可以声称这也正在完成:按值)。出于这个原因,“Java 仅按值传递”语句实际上会引起很多混乱,但实际上,该断言需要理解原语是按值传递的,而对象 references 是按值传递的。
    【解决方案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)中是不可变的。我能想到的替代方案是:

      1. 将结果字符串作为返回值返回。
      2. 不要使用 String 参数,而是使用 StringBuffer 或 StringBuilder,它们不是不可变的。

      在第二种情况下,您会遇到类似的情况:

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

      编辑

      如果我理解正确,您想将参数用作局部变量。你不能,因为所有方法参数都是 Scala 中的 val。 唯一要做的就是先把它复制到一个局部变量中:

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

      【讨论】:

      • 你好。谢谢你。恐怕你误解了我的问题。我已经更新了。
      • 广告 2。不是一个选项,那只是混乱。 StringBuilder 暗示我将构建一些大字符串。如果我可以做“String(->也许是新字符串)”,为什么还要做“String -> StringBuilder -> String”?您的解决方案只是缺乏 Scala 知识或 Scala 限制的一种解决方法。你不会在 Java 中这样做,对吗?
      • 我并没有说使用 StringBuilder 很优雅,但是如果您不想将字符串作为返回值返回,那么您必须使用一些可变字符串类型的参数。实际上你可以直接在 Scala 中的 StringBuilder 上使用 endsWith()(你不需要调用 toString())。
      • 关于原始问题:Scala 鼓励函数式编程,因此即使使用普通 var(局部变量)也应尽可能避免。
      • @woky:来自 Odersky(Scala 的发明者)等人:Scala 编程,第 2 版,第 52 页:“Scala 允许您以命令式风格进行编程,但鼓励您采用更具功能性的风格”和后来的“Scala 鼓励你倾向于使用 vals,但最终会根据手头的工作找到最好的工具。”因此,根据其发明者的说法,Scala 确实鼓励进行函数式编程,尽管它并不强迫您这样做。另外,Perl 中的闭包与此有何关系?
      【解决方案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 中是不允许的。其他人已经描述了一些低级别的解决方法(都很好),但我会添加一个更高级别的解决方法。出于这种字符串规范化的目的,我使用 suffix、prefix、removeSuffix 和 removePrefix 等方法对 scala.String 进行拉皮条扩展。后缀和前缀将一个字符串附加或附加到另一个字符串上,除非后缀或前缀已经存在。 removeSuffix 和 removePrefix 做的很明显,从另一个字符串的结尾或开头删除一个字符串,如果它存在的话。您的用例将被编写

        val normalizedPath = destPath.addSuffix("/") 
        

        如果你做一堆数据分析或文件操作,这些方法非常得心应手,你不会相信没有它们你曾经做过。

        【讨论】:

        • 您在StringLike 中签出stripPrefixstripSuffix 了吗?听起来他们已经做了你的 removePrefixremoveSuffix 所做的事情。
        【解决方案7】:

        我知道这是一个老问题,但如果您只是想重用参数名称:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-11-17
          • 2018-06-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多