我的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)
}
}
}
答案 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
}
}