是否可以使用类似于Scala中where-clauses的内容?也许有一些我没想到的伎俩?
编辑:
感谢您的所有答案,我们非常感谢.总结一下:局部变量,vals和defs可以用来实现几乎相同的东西.对于惰性求值,可以使用lazy val(带隐式缓存)或函数定义.确保功能纯度留给程序员.
现在只剩下一个问题:是否有一种方法可以在使用它们的表达式之后放置值或函数定义?有时这似乎更清晰.这可以使用类或对象的字段/方法,但它似乎不适用于方法.
到目前为止,答案中没有提到另一件事.where-clause也限制了它们中定义的表达式的范围.我还没有找到在Scala中实现这一目标的方法.
在Hakell中,子句对函数进行局部定义.Scala没有明确的where子句,但是通过本地var
,val
和def
.可以实现相同的功能.
在斯卡拉:
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 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编译器:).
可以以类似的方式实现更复杂的示例(例如,使用两种语言都支持的模式匹配).本地函数具有与任何其他函数完全相同的语法,只是它们的作用域是它们所在的块.
编辑:另见丹尼尔对这样一个例子的回答以及对这个问题的一些阐述.
编辑2:添加了关于lazy
var
s和val
s 的讨论.
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解释器中的代码并查看打印输出!(我不会把它放在这里,因为这个答案已经很长了).
如果使用无参数函数而不是惰性变量,则可以获得类似的结果.在上面的示例中,您可以将每个替换lazy val
为a def
并获得类似的结果.不同之处在于惰性变量被缓存(也就是仅评估一次),但def
每次调用时都会评估a .
编辑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()
像预期的那样.的定义bar
中innerFun()
隐藏bar
在封闭范围限定.此外,该函数fun
是其封闭块的本地函数,因此不能使用它.方法doesNotCompile()
......不编译.有趣的是,println()
来自smthDifferent()
方法的两个调用都打印出来inner scope
.因此,是的,你可以在方法中使用它们之后放置定义!我不建议,因为我认为这是不好的做法(至少在我看来).在类文件中,您可以根据需要排列方法定义,但我会def
在使用之前将所有s 保留在函数中.并且val
s和var
s ......好吧......我发现在使用它们之后把它们放进去是很尴尬的.
另请注意,每个块必须以不带定义的表达式结束,因此您不能在块的末尾具有所有定义.我可能会把所有定义放在一个块的开头,然后写出我的所有逻辑,在该块的结尾产生一个结果.它似乎更自然,而不是:
{ // some logic // some defs // some other logic, returning the result }
正如我之前所说,你不能只用一个块结束// some defs
.这是Scala与Haskell略有不同的地方:).
编辑4:在Kim的评论的推动下,详细阐述了使用它们后的定义.
在具有副作用的语言中实现这是一件棘手的事情.在纯粹的无副作用世界中,顺序并不重要(方法不会取决于任何副作用).但是,正如斯卡拉允许副作用,对地方,你定义一个函数做的事情.此外,当您定义一个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) }
你给出的例子确实有效,如果val
s是lazy
或者是def
s.
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
相似,是的.我不会像Flaviu那样详细介绍细节,但我会举一个维基百科的例子.
哈斯克尔:
calc :: String -> [Float] calc = foldl f [] . words where f (x:y:zs) "+" = (y + x):zs f (x:y:zs) "-" = (y - x):zs f (x:y:zs) "*" = (y * x):zs f (x:y:zs) "/" = (y / x):zs f xs y = read y : xs
这些定义只是本地定义calc
.所以,在Scala中,我们会这样做:
def calc(s: String): List[Float] = { def f(s: List[Float], op: String) = (s, op) match { case (x :: y :: zs, "+") => (y + x) :: zs case (x :: y :: zs, "-") => (y - x) :: zs case (x :: y :: zs, "*") => (y * x) :: zs case (x :: y :: zs, "/") => (y / x) :: zs case (xs, y) => read(y) :: xs } s.words.foldLeft(List[Float]())(f) }
由于Scala没有等价物read
,因此您可以将其定义如下,以便运行此特定示例:
def read(s: String) = s.toFloat
words
尽管Scala 很容易定义,Scala也没有太多让我懊恼的事情:
implicit toWords(s: String) = new AnyRef { def words = s.split("\\s") }
现在,由于各种原因,Haskell的定义更加紧凑:
它具有更强大的类型推断,因此calc
不需要声明超出其自身类型的任何内容.Scala无法做到这一点,因为有意识的设计决策是使用类模型进行面向对象的.
它具有隐式模式匹配定义,而在Scala中,您必须声明该函数,然后声明模式匹配.
就简洁而言,它对currying的处理明显优于Scala.这是关于类模型和操作符号的各种决策的结果,其中处理curry被认为不那么重要.
Haskell对列表有特殊处理,可以为它们提供更简洁的语法.在Scala中,列表被视为与任何其他类一样,相反,要确保任何类都可以像Scala中的List一样紧凑.
因此,为什么Scala会做它的功能有多种原因,尽管我喜欢隐式模式匹配定义.:-)