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