Scala扩展while循环到do-until表达式

时间:2012-11-25 16:46:21

标签: scala

我正在尝试使用Scala进行一些实验。我想重复这个实验(随机)直到预期结果出来并得到结果。如果我用while或do-while循环执行此操作,那么我需要编写(假设'body'表示实验,'cond'表示是否预期):

do {
  val result = body
} while(!cond(result))

然而,它不起作用,因为最后一个条件不能引用循环体中的局部变量。我们需要像这样修改这个控件抽象:

def repeat[A](body: => A)(cond: A => Boolean): A = {
  val result = body
  if (cond(result)) result else repeat(body)(cond)
}

它以某种方式工作,但对我来说并不完美,因为我需要通过传递两个参数来调用此方法,例如:

val result = repeat(body)(a => ...)

我想知道是否有一种更有效,更自然的方法来实现这一点,使它看起来更像是内置结构:

val result = do { body } until (a => ...)

在这篇文章中找到了一个没有返回值的身体的优秀解决方案How Does One Make Scala Control Abstraction in Repeat Until?,最后一个单行答案。该答案中的body部分不返回值,因此until可以是新AnyRef对象的方法,但这个技巧不适用于此,因为我们想要返回A而不是AnyRef。有没有办法实现这个目标?感谢。

3 个答案:

答案 0 :(得分:6)

你正在混合编程风格并因此而陷入困境。

您的循环仅适用于加热处理器,除非您在其中产生某种副作用。

do {
  val result = bodyThatPrintsOrSomething
} until (!cond(result))

所以,如果你要使用副作用代码,只需将条件放入var:

var result: Whatever = _
do {
  result = bodyThatPrintsOrSomething
} until (!cond(result))

或同等的:

var result = bodyThatPrintsOrSomething
while (!cond(result)) result = bodyThatPrintsOrSomething

或者,如果采用功能方法,则无论如何都必须返回计算结果。然后使用类似的东西:

Iterator.continually{ bodyThatGivesAResult }.takeWhile(cond)

Iterator有一个已知的烦恼:repeat没有做好所有好的工作加上列表中的第一个坏工作。)

或者您可以使用尾随递归的javap -c方法。如果您不相信它,请检查字节码(使用@annotation.tailrec),添加var注释,以便编译器如果不是尾递归则会抛出错误,或将其写为while循环使用def repeat[A](body: => A)(cond: A => Boolean): A = { var a = body while (cond(a)) { a = body } a } 方法:

{{1}}

答案 1 :(得分:5)

通过微小的修改,您可以使用一种非常流畅的API来转换当前的方法,从而产生接近您想要的语法:

class run[A](body: => A) {
  def until(cond: A => Boolean): A = {
    val result = body
    if (cond(result)) result else until(cond)
  }
}
object run {
  def apply[A](body: => A) = new run(body)
}

由于do是保留字,因此我们必须使用run。结果现在看起来像这样:

run {
  // body with a result type A
} until (a => ...)

修改

我刚刚意识到我几乎彻底改变了链接question中已经提出的内容。将该方法扩展为返回类型A而不是Unit的一种可能性是:

def repeat[A](body: => A) = new {
  def until(condition: A => Boolean): A = {
    var a = body
    while (!condition(a)) { a = body }
    a     
  }   
}

答案 2 :(得分:0)

为了记录前面提出的建议的衍生物,我采用了repeat { ... } until(...)的尾递归实现,其中还包括迭代次数的限制:

def repeat[A](body: => A) = new {
  def until(condition: A => Boolean, attempts: Int = 10): Option[A] = {
    if (attempts <= 0) None
    else {
      val a = body
      if (condition(a)) Some(a)
      else until(condition, attempts - 1)
    }
  }
}

这允许循环在attempts执行身体后纾困:

scala> import java.util.Random
import java.util.Random

scala> val r = new Random()
r: java.util.Random = java.util.Random@cb51256

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res0: Option[Int] = Some(98)

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res1: Option[Int] = Some(98)

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res2: Option[Int] = None

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res3: Option[Int] = None

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res4: Option[Int] = Some(94)