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)