是否允许您从范围中删除变量?

时间:2014-11-20 01:37:19

标签: scala programming-languages

我的Scala代码中偶尔会出现一些错误,当我为变量执行大量操作时,会创建大量的中间变量。

class SomeClass(name: String) {

  def doSomeThings() {
    val canonicalizedName = canonicalizeName(name)
    val boozbangledName = boozbangle(canonicalizedName)
    val plobberedName = plobber(boozbangledName)
    val flemmedName = flem(boozbangledName) // bug!  should have been flem(plobberedName)
    // (more code)
    doAThing(flemmedName) // correct!
    doAnotherThing(name)     // bug!  should have been doAnotherThing(flemmedName)
  }
}

有没有办法在任何编程语言中防止此类错误 - 例如从范围中删除“已弃用”变量?我可以使用var来防止第一个错误,但它仍然无法阻止第二个错误:

class SomeClass(name: String) {

  def doSomeThings() {
    var fixedName = name
    fixedName = canonicalizeName(fixedName)
    fixedName = boozbangle(fixedName)
    fixedName = plobber(fixedName)
    fixedName = flem(fixedName)
    // (more code)
    doAThing(fixedName) // good!
    doAnotherThing(name)     // bug!  should have been doAnotherThing(flemmedName)
  }
}

}

3 个答案:

答案 0 :(得分:2)

虽然与变量名称混淆可能表示命名,重复或违反demeter法则,但实际上可以使用宏在Scala中实现“val-deprecation”功能。

由于我刚开始学习如何编写宏,我无法为您提供令人满意的实现,但是 - 为了说明我的想法 - 我试图提出最简单的实现:检查val的名称是否是包含在表达式中。此外,宏仅返回Unit表达式,可以使用泛型类型参数进行推广。但是,应该可以提出更好看,更强大的实现。

请注意,需要在其他代码之前编译宏,因此需要在单独的项目中实现它们。

天真的宏实现可能如下所示:

package mymacros

object Macros {
  import scala.language.experimental.macros

  def deprecatedValue_impl(c: scala.reflect.macros.blackbox.Context)(value: c.Expr[Any])(action: c.Tree): c.Tree = {
    import c.universe._
    val valueName = show(value.tree)
    if(show(action).contains(valueName)) {
      throw DeprecatedValueUsed(valueName)
    }
    action
  }

  def deprecatedValue(value: Any)(action: => Unit): Unit = macro deprecatedValue_impl

}

case class DeprecatedValueUsed(valueName: String) extends Error // Add a nice error message here

如果action包含所提供的val的标识符,那么在编译时会产生错误。

object DeprecateNames {
  import mymacros.Macros._

  def main(args: Array[String]): Unit = {
    val flobberwobber = "hello"
    val flabberwabber = "world"
    deprecatedValue(flobberwobber){
      // println(flobberwobber) doesn't compile
      println(flabberwabber)
      // println(flobberwobber + flabberwabber)  doesn't compile
      println("hello" + "world")
      // println("flobberwobber") doesn't compile
    }
  }
}

请注意,根据SI-5778,使用Tree代替Expr从而允许按名称调用参数仅可用于Scala 2.11。

答案 1 :(得分:2)

看起来你真正想做的就是编写所有这些功能。这捕获了一系列操作的概念,没有任何中间值"泄漏"。假设它们都是Function1个实例(您可以使用_运算符来强制将方法视为函数),它们有一个andThen运算符,可以让您组合它们。所以你可以这样做:

class SomeClass(name: String) {
  def doSomeThings() {
    val processChain = Seq(canonicalizeName, boozbangle, plobber, flem, doAThing, doAnotherThing)
    processChain.reduce(_ andThen _)(name)
  }
}

换句话说,这些一元函数是semigroup,其组合(andThen)为运算符。嗯,实际上,他们还monoid identity函数作为身份,但我们在这里没有使用该属性,因为它&# 39;预定的功能清单;如果您想要fold变量列表,并使用identity作为基本案例,则可以发挥作用。

我遇到的另一件事是你可以使用类型系统来实现你想要的安全性。如果您想将中间值标记为"不适合"对于任何函数而不是它们的目标函数,您可能希望将单个参数case class定义为类似强制sigils,然后如果为函数提供适当的输入和输出类型,可以保留你的中间变量,但它们只是不会以类型允许的任何其他方式使用。

为了回答你是否有任何语言允许删除变量的问题,我知道Python有del keyword

答案 2 :(得分:2)

已经有很多好的建议。这是您可能会发现有助于解决此问题的另一项技术:

class SomeClass(name: String) {
  def doSomeThings() {
    val flemmedName = {
      val plobberedName = {
        val canonicalizedName = canonicalizeName(name)
        val boozbangledName = boozbangle(canonicalizedName)
        plobber(boozbangledName)
      }
      flem(boozbangledName) // compilation error
    }
    // (more code)
    doAThing(flemmedName) // correct!
    doAnotherThing(name)     // sorry, can't help with this one
  }
}