内置方式来模拟while循环而没有副作用

时间:2013-12-22 04:21:37

标签: scala functional-programming

通常,while循环的样板(r是我想要的结果,p是预测变量:

var r, p;
while(p()) {
  (r, p) = compute(r)
}

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

def f(r) = {
  val (nr, p) = compute(r)
  if(p()) nr
  else f(nr)
}

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

2 个答案:

答案 0 :(得分:4)

def compute(i: Int): (Int, () => Boolean) =
  (i - 1) -> { () => i > 1 }

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

Iterator.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>

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

如果您希望使用Iterator的解决方案,请this answerIterator.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

Scalaz展开

如果您使用的是scalaz,则已有此类方法。它产生Stream,所以你应该得到最后一个元素。

在迭代结束时,您应该生成OptionNone是一个退出条件),其中包含Pair流元素(r)和下一个状态{{ 1}}:

(r, p())

答案 1 :(得分:1)

我不知道这是否真的回答了“内置”的问题。我不相信有一个比你的递归例程更容易实现或理解的解决方案。但这是另一种攻击问题的方法。

您可以使用Iterator.iterate创建无限迭代器,然后找到第一个未通过谓词的元素。

// Count until we are greater than 5
def compute(r: Int): (Int, () => Boolean) = {
  (r + 1, () => (r < 5))
} 

// Start at the beginning
val r = 1
val p = () => true


// Create an infinite iterator of computations
val it = Iterator.iterate((r, p))({
  case (r, _) => compute(r)
})

// Find (optionally) the first element that fails p. Then get() the result.
val result = it.find({ case (_, p) => !p() })
  .map { _._1 }
  .get