斯卡拉相当于哈斯克尔的where-clause? [英] Scala equivalent to Haskell's where-clauses?

查看:112
本文介绍了斯卡拉相当于哈斯克尔的where-clause?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有可能在Scala中使用类似where子句的东西?也许有一些我并没有想到的伎俩?

编辑:

感谢所有的答案,他们非常感激。总结:
本地变量,vals和defs可以用来实现几乎相同的事情。对于懒惰评估,可以使用lazy val(隐式缓存)或函数定义。确保函数纯度由程序员决定。



现在只剩下一个问题了:是否有方法将值或函数定义放在它们使用的表达式之后?有时候这似乎更清晰。这是可能的类或对象的字段/方法,但它似乎并没有在方法内工作。



在答案中没有提到的另一件事至今。 where子句也限制了它们中定义的表达式的范围。我还没有找到一种在Scala中实现它的方法。

解决方案

在Hakell中,子句将局部定义保存到函数中。 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  - >整数 - >整数
foo xy = a - b
其中
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  - >整数 - >整数
foo xy = y + bar x
其中
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( - - 第1行:);
lazy val lazy1:Int = {print( - lazy1 evaluate); x ^ 2}
println();

print(--- Line 2:);
lazy val lazy2:Int = {print( - lazy2 evaluate); y ^ 2}
println();

print(--- Line 3:);
lazy val lazy3:Int = {print( - lazy3 evaluate)
while(true){} //无限循环!
x ^ 2 + y ^ 2}
println();

print(--- Line 4(if clause):);
if(x else lazy2 + lazy1
}

这里 lazy1 lazy2 lazy3 都是懒惰的变量。 lazy3 永远不会实例化(因此这段代码永远不会进入无限循环),并且 lazy1 lazy2 取决于函数的参数。例如,当你调用 foo(1,2)时,你会在 lazy2之前实例化 lazy1 ,当你调用 foo(2,1)时,你会得到相反的结果。尝试使用scala解释器中的代码并查看打印输出! (我不会把它放在这里,因为这个答案已经很长了)。



如果您使用无参数函数的惰性变量而不是相同的结果。在上面的例子中,你可以用 def 替换每一个 lazy val 并获得相似的结果。不同之处在于缓存的变量被缓存(也就是只计算一次),但每次调用时都会对 def 进行评估。



编辑3:添加了关于范围界定的讨论,请参阅问题。

局部定义范围



本地定义与预期一样具有所声明块的范围(大多数情况下,在很少的情况下,它们可以避开该块,就像使用mid- for循环中的流变量绑定)。因此,可以使用本地 var val def 来限制表达式的范围。以下例子:

  object Obj {
def bar =outer scope

)def innerFun(){
def bar =inner scope
println(bar)//打印内部范围
}

def outerFun(){
println(bar)//打印外部范围
}

def smthDifferent(){
println(bar)//打印内部范围! :)
def bar =inner scope
println(bar)//打印内部范围
}
$ b $ def不编译(){
{
def fun =fun//本地块到此块
42 //块不能以定义结束......
}
println(fun)
}

$ b

innerFun() code>和 outerFun()的行为与预期相同。 innerFun()中的 bar 的定义隐藏了在封闭范围内定义。另外,函数 fun 对于它的封闭块是本地的,所以它不能被使用。方法 doesNotCompile() ...不能编译。有意思的是,从 smthDifferent()方法调用 println() print 内部范围。因此,是的,您可以在内部方法中使用它们后进行定义!但我不会推荐,因为我认为这是不好的做法(至少在我看来)。在类文件中,您可以按照自己喜欢的方式来安排方法定义,但在使用函数之前,我会将所有 def s保留在函数中。和 val s和 var s ...呃...我觉得在使用它们。



另外请注意,每个块必须以 not 结尾,因此您不能在块的结尾处具有所有定义。我可能会把所有的定义放在一个块的开头,然后写下我所有的逻辑,在这个块的结尾处产生一个结果。它看起来更自然,而不是:

  {
//某些逻辑

// some defs

//其他逻辑,返回结果
}

正如我之前所说的,只有 //一些defs 才能结束块。这是Scala与Haskell略有不同的地方:)。

编辑4 :在使用它们后定义东西的详细介绍,由的评论。



使用它们后定义'stuff'



使用具有副作用的语言来实现这是一件棘手的事情。在纯粹无副作用的世界里,顺序并不重要(方法不会依赖于任何副作用)。但是,由于Scala允许副作用,您定义函数的地方确实很重要。另外,当你定义一个 val 或者 var 时,必须对右边进行评估以实例化 VAL 。考虑下面的例子:

  //不会编译:) 
def foo(x:Int)= {

// println *现在执行*,但
//不能调用f(10)作为关闭
//您调用的函数尚未创建!
//类似于调用一个空变量
println(f(10))

var aVar = 1

//闭包必须在这里创建,
//因为它不能捕获aVar,否则
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 (一个); b}
println(2)
lazy val b = {println(b); 1}
println(3)
a + a
}

这个例子很好地显示了工作中的缓存(尝试将 lazy val 更改为 def 并查看会发生什么情况:) p>

在一个有副作用的世界里,我仍然会这么做,最好在使用它们之前坚持定义。阅读源代码比较容易。



- Flaviu Cipcigan


Is it possible to use something similar to where-clauses in Scala? Maybe there is some trick I didn't think of?

Edit:

Thanks for all your answers, they are very much appreciated. To sum up: Local vars, vals and defs can be used to achieve almost the same thing. For lazy evaluation, one can use lazy val (with implicit caching) or function definitions. Ensuring functional purity is left to the programmer.

Now just one question remains: Is there a way of putting the value or function definitions after the expressions they are used in? Sometimes that seems a lot clearer. This is possible with fields/methods of a class or object, but it doesn't seem to work within methods.

One other thing that wasn't mentioned in the answers so far. where-clauses also limit the scope of the expressions defined in them. I haven't found a way to achieve that in Scala either.

解决方案

In Hakell, where clauses hold local definitions to a function. Scala does not have explicit where clauses, but the same functionality can be achieved by having local var, val and def.

Local `var` and `val`

In Scala:

def foo(x: Int, y: Int): Int = {
  val a = x + y 
  var b = x * y
  a - b
}

In Haskell:

foo :: Integer -> Integer -> Integer 
foo x y = a - b
        where 
          a = x + y
          b = x * y

Local `def`

In Scala

def foo(x: Int, y: Int): Int = {
  def bar(x: Int) = x * x
  y + bar(x)
}

In Haskell

foo :: Integer -> Integer -> Integer 
foo x y = y + bar x
         where 
           bar x = x * x

Please correct me if I have made any syntax errors in the Haskell example, as I currently have no Haskell compiler installed on this computer :).

More complicated examples can be achieved in similar ways (for example using pattern matching, which both languages have support for). Local functions have exactly the same syntax as any other function, just that their scope is the block they are in.

EDIT: Also see Daniel's answer for such an example and some elaboration on the subject.

EDIT 2: Added a discussion about lazy vars and vals.

Lazy `var` and `val`

Edward Kmett's answer rightly pointed out that Haskell's where clause has laziness and purity. You can do something very similar in Scala using lazy variables. These are only instantiated when needed. Consider the following example:

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
}

Here lazy1, lazy2 and lazy3 are all lazy variables. lazy3 is never instantiated (therefore this code never enters in an infinite loop) and the order of instantiation of lazy1 and lazy2 depends on the arguments of the function. For example when you call foo(1,2) you will get lazy1 instantiated before lazy2 and when you call foo(2,1) you will get the reverse. Try the code out in the scala interpreter and see the printout! (I won't put it here as this answer is already quite long).

You could achieve similar results if instead of lazy variables you used no-argument functions. In the example above, you could replace every lazy val with a def and achieve similar results. The difference is that lazy variables are cached (aka only evaluated once) but a def is evaluated every time it is invoked.

EDIT 3: Added a discussion about scoping, see question.

Scope of local definitions

Local definitions have the scope of the block they are declared in, as expected (well, most of the time, in rare situations they can escape the block, like when using mid-stream variable binding in for loops) . Therefore local var, val and def can be used to limit the scope of an expression. Take the following example:

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)
  }

}

Both innerFun() and outerFun() behave as expected. The definition of bar in innerFun() hides the bar defined in the enclosing scope. Also, the function fun is local to its enclosing block, so it cannot be used otherwise. The method doesNotCompile() ... does not compile. It is interesting to note that both println() calls from the smthDifferent() method print inner scope. Therefore, yes, you can put definitions after they are used inside methods! I wouldn't recommend though, as I think it is bad practice (at least in my opinion). In class files, you can arrange method definitions as you like, but I would keep all the defs inside a function before they are used. And vals and vars ... well ... I find it awkward to put them after they are used.

Also note that each block must end with an expression not with a definition, therefore you cannot have all the definitions at an end of a block. I would probably put all the definitions at the start of a block, and then write all my logic producing a result at the end of that block. It does seem more natural that way, rather then:

{
// some logic

// some defs

// some other logic, returning the result
}    

As I previously said, you cannot end a block with just // some defs. This is where Scala slightly differs from Haskell :).

EDIT 4: Elaborated on defining stuff after using them, prompted by Kim's comment.

Defining 'stuff' after using them

This is a tricky thing to implement in a language that has side effects. In a pure-no-side-effect world, the order would not be important (methods would not depend on any side-effects). But, as Scala allows side effects, the place where you define a function does matter. Also, when you define a val or var, the right hand side must be evaluated in place in order to instantiate that val. Consider the following example:

// 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)
}

The example you give does work though if the vals are lazy or they are defs.

def foo(): Int = {
  println(1)
  lazy val a = { println("a"); b }
  println(2)
  lazy val b = { println("b"); 1 }
  println(3)
  a + a
}

This example also nicely shows caching at work (try changing the lazy val to def and see what happens :)

I still thing in a world with side effects it's better to stick with having definitions before you use them. It's easier to read source code that way.

-- Flaviu Cipcigan

这篇关于斯卡拉相当于哈斯克尔的where-clause?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆