在 Hakell 中,where 子句包含函数的局部定义。 Scala 没有明确的 where 子句,但可以通过使用本地 var、val 和 def 来实现相同的功能。
本地`var`和`val`
在 Scala 中:
def foo(x: Int, y: Int): Int = {
val a = x + y
var b = x * y
a - b
}
在 Haskell 中:
foo :: Integer -> Integer -> Integer
foo x y = a - b
where
a = x + y
b = x * y
本地`def`
在 Scala 中
def foo(x: Int, y: Int): Int = {
def bar(x: Int) = x * x
y + bar(x)
}
在 Haskell 中
foo :: Integer -> Integer -> Integer
foo x y = y + bar x
where
bar x = x * x
如果我在 Haskell 示例中出现任何语法错误,请纠正我,因为我目前在这台计算机上没有安装 Haskell 编译器:)。
更复杂的示例可以通过类似的方式实现(例如使用两种语言都支持的模式匹配)。局部函数与任何其他函数的语法完全相同,只是它们的作用域是它们所在的块。
编辑:另请参阅Daniel 的回答以获取此类示例以及有关该主题的一些详细说明。
EDIT 2:添加了关于lazyvars 和vals 的讨论。
懒惰的`var`和`val`
Edward Kmett 的回答正确地指出了 Haskell 的 where 子句具有惰性和纯洁性。你可以在 Scala 中使用lazy 变量做一些非常相似的事情。这些仅在需要时实例化。考虑以下示例:
def foo(x: Int, y: Int) = {
print("--- Line 1: ");
lazy val lazy1: Int = { print("-- lazy1 evaluated "); x^2}
println();
print("--- Line 2: ");
lazy val lazy2: Int = { print("-- lazy2 evaluated "); y^2}
println();
print("--- Line 3: ");
lazy val lazy3: Int = { print("-- lazy3 evaluated ")
while(true) {} // infinite loop!
x^2 + y^2 }
println();
print("--- Line 4 (if clause): ");
if (x < y) lazy1 + lazy2
else lazy2 + lazy1
}
这里lazy1、lazy2和lazy3都是惰性变量。 lazy3 永远不会被实例化(因此此代码永远不会进入无限循环),lazy1 和 lazy2 的实例化顺序取决于函数的参数。例如,当您调用foo(1,2) 时,您将在lazy2 之前实例化lazy1,而当您调用foo(2,1) 时,您将得到相反的结果。在 scala 解释器中尝试代码并查看打印输出! (我不会放在这里,因为这个答案已经很长了)。
如果您使用无参数函数而不是惰性变量,您可以获得类似的结果。在上面的示例中,您可以将每个 lazy val 替换为 def 并获得类似的结果。不同之处在于惰性变量被缓存(也就是只计算一次),但 def 在每次调用时都会被计算。
编辑 3: 添加了关于范围界定的讨论,请参阅问题。
本地定义的范围
本地定义具有声明它们的块的范围,正如预期的那样(嗯,大多数时候,在很少的情况下,它们可以逃脱块,例如使用中流变量绑定时在 for 循环中)。因此本地var、val 和def 可用于限制表达式的范围。举个例子:
object Obj {
def bar = "outer scope"
def innerFun() {
def bar = "inner scope"
println(bar) // prints inner scope
}
def outerFun() {
println(bar) // prints outer scope
}
def smthDifferent() {
println(bar) // prints inner scope ! :)
def bar = "inner scope"
println(bar) // prints inner scope
}
def doesNotCompile() {
{
def fun = "fun" // local to this block
42 // blocks must not end with a definition...
}
println(fun)
}
}
innerFun() 和 outerFun() 都按预期运行。 innerFun() 中bar 的定义隐藏了封闭范围内定义的bar。此外,函数fun 是其封闭块的本地函数,因此不能以其他方式使用。方法doesNotCompile() ... 无法编译。有趣的是,来自smthDifferent() 方法的两个println() 调用都会打印inner scope。因此,是的,您可以将定义放在方法中使用后!不过我不推荐,因为我认为这是不好的做法(至少在我看来)。在类文件中,您可以随意安排方法定义,但我会在使用之前将所有defs 保留在函数中。还有vals 和vars ……嗯……我觉得用完之后再放它们很尴尬。
另外请注意,每个块必须以带有定义的表达式 not 结尾,因此您不能将所有定义都放在一个块的末尾。我可能会将所有定义放在块的开头,然后在该块的末尾编写所有产生结果的逻辑。这样看起来确实更自然,而不是:
{
// some logic
// some defs
// some other logic, returning the result
}
正如我之前所说,你不能只用// some defs 结束一个块。这是 Scala 与 Haskell 略有不同的地方:)。
编辑 4:详细说明使用后定义的东西,由Kim 的评论提示。
使用后定义“东西”
在具有副作用的语言中实现这是一件棘手的事情。在纯无副作用的世界中,顺序并不重要(方法不依赖于任何副作用)。但是,由于 Scala 允许副作用,您定义函数的位置确实很重要。此外,当您定义 val 或 var 时,必须在适当的位置评估右侧以便实例化 val。考虑以下示例:
// does not compile :)
def foo(x: Int) = {
// println *has* to execute now, but
// cannot call f(10) as the closure
// that you call has not been created yet!
// it's similar to calling a variable that is null
println(f(10))
var aVar = 1
// the closure has to be created here,
// as it cannot capture aVar otherwise
def f(i: Int) = i + aVar
aVar = aVar + 1
f(10)
}
如果vals 是lazy 或者它们是defs,您给出的示例确实有效。
def foo(): Int = {
println(1)
lazy val a = { println("a"); b }
println(2)
lazy val b = { println("b"); 1 }
println(3)
a + a
}
这个例子也很好地展示了缓存在工作中的作用(尝试将 lazy val 更改为 def 看看会发生什么:)
我仍然在一个有副作用的世界里,最好在使用它们之前坚持定义。这样更容易阅读源代码。
-- Flaviu Cipcigan