内置的方式来模拟一个没有副作用的 while 循环

Lai*_*uan 3 functional-programming scala

通常while循环的样板看起来像(r是我想要的结果,p是预测器:

var r, p;
while(p()) {
  (r, p) = compute(r)
}
Run Code Online (Sandbox Code Playgroud)

我可以将其转换为递归以摆脱var

def f(r) = {
  val (nr, p) = compute(r)
  if(p()) nr
  else f(nr)
}
Run Code Online (Sandbox Code Playgroud)

有没有内置的方法来实现这种逻辑?我知道Iterator.continually,但似乎仍然需要var存储副作用。

sen*_*nia 5

def compute(i: Int): (Int, () => Boolean) =
  (i - 1) -> { () => i > 1 }
Run Code Online (Sandbox Code Playgroud)

要创建一个不可变的,while您将需要iteration- 一个接受state并返回state相同类型的新函数以及退出条件的函数。

迭代器.continually

这不是最好的解决方案 - 在我看来,这段代码很难阅读,但既然你提到了它:

val (r, p) = Iterator.continually(()).
  scanLeft( 13 -> { () => true } ){
    case ((r, p), _) => compute(r)
  }.dropWhile{ case (r, p) => p() }.
  next
// r: Int = 0
// p: () => Boolean = <function0>
Run Code Online (Sandbox Code Playgroud)

您可以使用,val (r, _) =因为您不需要p.

如果你想了一个办法Iterator看到这个答案Iterator.iterate

尾递归

我想这是一个惯用的解决方案。您始终可以将while循环重写为具有显式状态类型的尾递归:

@annotation.tailrec
def lastWhile[T](current: T)(f: T => (T, () => Boolean)): T = {
  val (r, p) = f(current)
  if (p()) lastWhile(r)(f)
  else r
}

lastWhile(13){ compute }
// Int = 0
Run Code Online (Sandbox Code Playgroud)

斯卡拉兹展开

如果您正在使用scalaz,已经有这样的方法。它产生一个Stream,所以你应该得到最后一个元素。

在迭代结束时,您应该使用流元素 ( ) 和下一个状态生成Option(None是退出条件) :Pairr(r, p())

unfold(13 -> true) { case (r0, p0) =>
  val (r, p) = compute(r0)
  p0.option(r -> (r, p()))
}.last
// Int = 0
Run Code Online (Sandbox Code Playgroud)