9

我正在尝试用 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 => ...)

在这篇文章中找到了一个针对没有返回值的 body 的出色解决方案:如何在重复直到之前使 Scala 控制抽象?,最后一个单行答案。它body在该答案中的部分不返回值,因此until可以是新AnyRef对象的方法,但该技巧不适用于此处,因为我们想要返回A而不是AnyRef. 有什么办法可以做到这一点?谢谢。

4

3 回答 3

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
}
于 2012-11-25T20:31:55.533 回答
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 => ...)

编辑:

我刚刚意识到我几乎重新发明了链接问题中已经提出的内容。扩展该方法以返回类型A而不是的一种可能性Unit是:

def repeat[A](body: => A) = new {
  def until(condition: A => Boolean): A = {
    var a = body
    while (!condition(a)) { a = body }
    a     
  }   
}
于 2012-11-26T09:32:58.223 回答
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)
于 2016-04-12T21:44:26.903 回答