Do any languages let you remove a variable from scope?

267 Views Asked by At

There's an occasional source of bugs in my Scala code, when I do a number of operations in order to a variable, creating lots of intermediate variables.

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)
  }
}

Is there any way to prevent this sort of bug, in any programming language - e.g. remove "deprecated" variables from scope? I could use a var to prevent the first bug, but it still doesn't prevent the second:

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

There are 3 best solutions below

0
On

While getting confused with the variable names may indicate bad naming, duplication or violation of the law of demeter, it is actually possible to implement a "val-deprecation" feature in Scala using macros.

As I have just started learning how to write macros I cannot provide you a satisfying implementation, but - to illustrate what I have in mind - I tried to come up with the most trivial implementation: Checking whether the name of the val is contained in the expressions. Also, the macro only returns a Unit expression, which could be generalized using generic type parameters. However, it should be possible to come up with a better looking, more powerful implementation.

Note that macros need to be compiled before your other code, so you need to implement them in a separate project.

The naive macro implementation could look as follows:

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

Which would produce errors at compile time, if action contains the identifier of the provided 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
    }
  }
}

Note that, according to SI-5778, using Tree instead of Expr and thereby allowing call-by-name parameters is only possible since Scala 2.11.

0
On

It seems like what you really want to do is compose all of those functions. That captures the idea of a chain of operations, with none of the intermediate values "leaking out". Assuming they were all Function1 instances (you could use the _ operator to coerce methods to be treated as functions), they have an andThen operator that lets you compose them. So you could do something like:

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

In other words, these unary functions are a semigroup, with composition (andThen) as the operator. Well, really, they're furthermore a monoid with the identity function as the identity, but we're not using that property here, since it's a predetermined list of functions; that would come into play if you wanted to fold a variable list, and use identity as the base case.

The other thing that occurred to me is that you can use the type system to accomplish the safety you want as well. If you want to tag intermediate values as being "unsuitable" for any function but the one their destined for, you might want to define single argument case classes to act kind of like enforced sigils, and then if you give your functions the appropriate input and output types, you can keep your intermediate variables, but they just won't be usable in any other way than the types will allow.

And to answer your question of whether any language allows removal of variables, I know that Python has the del keyword for this.

0
On

Lots of good suggestions already. Here's another technique that you might find helpful for this problem:

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
  }
}