当前位置:  开发笔记 > 编程语言 > 正文

Scala相当于Haskell的where-clauses?

如何解决《Scala相当于Haskell的where-clauses?》经验,为你挑选了2个好方法。

是否可以使用类似于Scala中where-clauses的内容?也许有一些我没想到的伎俩?

编辑:

感谢您的所有答案,我们非常感谢.总结一下:局部变量,vals和defs可以用来实现几乎相同的东西.对于惰性求值,可以使用lazy val(带隐式缓存)或函数定义.确保功能纯度留给程序员.

现在只剩下一个问题:是否有一种方法可以在使用它们的表达式之后放置值或函数定义?有时这似乎更清晰.这可以使用类或对象的字段/方法,但它似乎不适用于方法.

到目前为止,答案中没有提到另一件事.where-clause也限制了它们中定义的表达式的范围.我还没有找到在Scala中实现这一目标的方法.



1> Flaviu Cipci..:

在Hakell中,子句对函数进行局部定义.Scala没有明确的where子句,但是通过本地var,valdef.可以实现相同的功能.

本地`var`和`val`

在斯卡拉:

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`

在斯卡拉

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 vars和vals 的讨论.

懒惰`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永远不会实例化(因此这段代码永远不会进入无限循环)和实例化的顺序,lazy1lazy2取决于函数的参数.例如,当你打电话给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()像预期的那样.的定义barinnerFun()隐藏bar在封闭范围限定.此外,该函数fun是其封闭块的本地函数,因此不能使用它.方法doesNotCompile()......不编译.有趣的是,println()来自smthDifferent()方法的两个调用都打印出来inner scope.因此,是的,你可以在方法中使用它们之后放置定义!我不建议,因为我认为这是不好的做法(至少在我看来).在类文件中,您可以根据需要排列方法定义,但我会def在使用之前将所有s 保留在函数中.并且vals和vars ......好吧......我发现在使用它们之后把它们放进去是很尴尬的.

另请注意,每个块必须以带定义的表达式结束,因此您不能在块的末尾具有所有定义.我可能会把所有定义放在一个块的开头,然后写出我的所有逻辑,在该块的结尾产生一个结果.它似乎更自然,而不是:

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

你给出的例子确实有效,如果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 valdef,看看会发生什么:)

我仍然处在一个有副作用的世界中,最好使用它们之前坚持使用定义.以这种方式阅读源代码更容易.

-- Flaviu Cipcigan



2> Daniel C. So..:

相似,是的.我不会像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会做它的功能有多种原因,尽管我喜欢隐式模式匹配定义.:-)

推荐阅读
360691894_8a5c48
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有