在 Hakell 中,where 子句包含函数的局部定义。Scala 没有明确的 where 子句,但是可以通过使用 local 和 来实现相同var
的val
功能def
。
本地`var`和`val`
在斯卡拉:
def foo(x: Int, y: Int): Int = {
val a = x + y
var b = x * y
a - b
}
在哈斯克尔:
foo :: Integer -> Integer -> Integer
foo x y = a - b
where
a = x + y
b = x * y
本地`def`
在斯卡拉
def foo(x: Int, y: Int): Int = {
def bar(x: Int) = x * x
y + bar(x)
}
在哈斯克尔
foo :: Integer -> Integer -> Integer
foo x y = y + bar x
where
bar x = x * x
如果我在 Haskell 示例中犯了任何语法错误,请纠正我,因为我目前没有在这台计算机上安装 Haskell 编译器:)。
更复杂的示例可以通过类似的方式实现(例如使用两种语言都支持的模式匹配)。局部函数与任何其他函数具有完全相同的语法,只是它们的作用域是它们所在的块。
编辑:另请参阅Daniel对此类示例的回答以及有关该主题的一些详细说明。
编辑 2:添加了关于lazy
var
s 和val
s 的讨论。
懒惰的`var`和`val`
Edward Kmett的回答正确地指出了 Haskell 的 where 子句具有惰性和纯洁性。lazy
你可以在 Scala 中使用变量做一些非常相似的事情。这些仅在需要时实例化。考虑以下示例:
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)
您将lazy1
在之前实例化,lazy2
而当您调用时,foo(2,1)
您将得到相反的结果。在 scala 解释器中尝试代码并查看打印输出!(我不会把它放在这里,因为这个答案已经很长了)。
如果您使用无参数函数而不是惰性变量,您可以获得类似的结果。在上面的示例中,您可以将 every 替换lazy val
为 adef
并获得类似的结果。不同之处在于惰性变量被缓存(也就是只评估一次),但def
每次调用它时都会评估 a 。
编辑 3:添加了关于范围界定的讨论,请参阅问题。
本地定义的范围
正如预期的那样,局部定义具有声明它们的块的范围(嗯,大多数情况下,在极少数情况下,它们可以逃脱该块,例如在 for 循环中使用中流变量绑定时)。因此 localvar
和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()
预期运行。bar
in的定义innerFun()
隐藏bar
了封闭范围内的定义。此外,该函数fun
对其封闭块是本地的,因此不能以其他方式使用。该方法doesNotCompile()
... 无法编译。有趣的是,这两个println()
调用都来自smthDifferent()
方法 print inner scope
。因此,是的,您可以将定义放在方法中使用后!不过我不推荐,因为我认为这是不好的做法(至少在我看来)。在类文件中,您可以随意安排方法定义,但我会def
在使用之前将所有 s 保留在函数中。还有val
s 和var
s ……嗯……我觉得用完之后再放就很尴尬。
另请注意,每个块必须以表达式而不是定义结尾,因此您不能将所有定义都放在块的末尾。我可能会将所有定义放在块的开头,然后在该块的末尾编写所有产生结果的逻辑。这样看起来确实更自然,而不是:
{
// some logic
// some defs
// some other logic, returning the result
}
正如我之前所说,你不能只用// some defs
. 这是 Scala 与 Haskell 略有不同的地方:)。
编辑 4 :根据Kim的评论,在使用它们后详细说明了定义内容。
使用它们后定义“东西”
在具有副作用的语言中实现这是一件棘手的事情。在纯无副作用的世界中,顺序并不重要(方法不依赖于任何副作用)。但是,由于 Scala 允许副作用,定义函数的位置确实很重要。此外,当您定义val
or时var
,必须在适当的位置评估右侧以实例化 that 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)
}
val
如果s 是lazy
或它们是s ,您给出的示例确实有效def
。
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
todef
看看会发生什么:)
我仍然在一个有副作用的世界里,最好在使用它们之前坚持定义。这样更容易阅读源代码。
-- Flaviu Cipcigan