Scala, расширяющая циклы while для выражения do-until

Я пытаюсь провести эксперимент со Scala. Я хотел бы повторить этот эксперимент (рандомизированный), пока не выйдет ожидаемый результат и не получит этот результат. Если я делаю это с помощью цикла while или do-while, мне нужно написать (предположим, что "тело" представляет эксперимент, а "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 => ...)

одно превосходное решение для тела без возвращаемого значения нашел в этом посте: Как сделать абстракцию управления Scala в повторе Пока?, последний однострочный ответ. Его body часть в этом ответе не возвращает значение, поэтому until может быть методом новый AnyRef объект, но этот трюк не применяется здесь, так как мы хотим вернуться A, а не AnyRef. Есть ли способ достичь этого? Спасибо.

3 ответов


С незначительной модификацией вы можете превратить свой текущий подход в своего рода mini fluent 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 => ...)

Edit:

Я только что понял, что я почти изобрел то, что уже было предложено в linked вопрос. Одна возможность расширить это подход для возврата типа A вместо Unit будет:

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

вы смешиваете стили программирования и получаете проблемы из-за этого.

ваш цикл хорош только для нагревания вашего процессора, если вы не делаете какой-то побочный эффект внутри него.

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 аннотация, поэтому компилятор выдаст ошибку, если она не является хвостовой рекурсивной, или напишет ее как цикл while, используя var метод:

def repeat[A](body: => A)(cond: A => Boolean): A = {
  var a = body
  while (cond(a)) { a = body }
  a
}

просто чтобы документировать производную от предложений, сделанных ранее, я пошел с хвостовой рекурсивной реализацией 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)