Повторяющийся вызов функции до ее возвращения никто

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

пусть это будет функция f: A => Option[B]. Я хотел бы сделать повторяющийся вызов f начиная с x, f(f(f(x).get).get...) до f возвращает None и сохранить последний неNone значение.

я написал реализацию для этого:

@tailrec
def recurrentCallUntilNone[B](f: B => Option[B], x: B): B = f(x) match {
  case Some(y) => recurrentCallUntilNone(f, y)
  case None => x
}

это уже реализовано в стандартной библиотеке?

использование пример для этого может быть для списка (молнии), который сохраняет текущую позицию. Позвонив next, None возвращается, если нет элементов после текущей позиции или Option для того же списка, но с увеличением текущей позиции. Используя вышеуказанный метод, an end метод может быть построен, который ищет список до конца.

4 ответов


то, что вы делаете, похоже на очень специфический тип батут. Более общий случай использует функции, обернутые в классы case вместо Option и поддерживает различные типы аргументов и возвращаемых типов.

как отмечает Калин-Андрей, батуты доступны в стандартной библиотеке с помощью TailCalls объект.

из первой ссылки:

def even2(n: Int): Bounce[Boolean] = {
  if (n == 0) Done(true)
  else Call(() => odd2(n - 1))
}
def odd2(n: Int): Bounce[Boolean] = {
  if (n == 0) Done(false)
  else Call(() => even2(n - 1))
}
trampoline(even2(9999))

sealed trait Bounce[A]
case class Done[A](result: A) extends Bounce[A]
case class Call[A](thunk: () => Bounce[A]) extends Bounce[A]

def trampoline[A](bounce: Bounce[A]): A = bounce match {
  case Call(thunk) => trampoline(thunk())
  case Done(x) => x
}

Теперь со стандартной библиотекой

import scala.util.control.TailCalls._

def even2(n: Int): TailRec[Boolean] = {
  if (n == 0) done(true)
  else tailcall(odd2(n - 1))
}
def odd2(n: Int): TailRec[Boolean] = {
  if (n == 0) done(false)
  else tailcall(even2(n - 1))
}
even2(9999).result

как насчет:

Stream.iterate(Some(x)) { x => x.flatMap(f _) }.takeWhile { _.isDefined }.last

обновление

или даже аккуратнее имхо (только один обход):

val result = {
  val xs = Stream.iterate(Some(x)) { x => x.flatMap(f _) }
  (xs zip xs.tail) collectFirst {
    case (Some(x), None) => x
  } get
}

обратите внимание, что безопасно звонить collectFirst потому что мы не может начинаться с None (в противном случае возможен бесконечный цикл).


в случае конкурса красоты вы можете построить функцию, которая преобразует существующую в монстра, о котором вы говорили, с помощью карринга.

def composeUntilTheEnd[B](f: Option[B] => Option[B])(x: Option[B]): Option[B] = 
        if (f(x) != None) composeUntilTheEnd(f)(f(x))
        else x

def ff = composeUntilTheEnd((x:Option[Int]) => x)_

называют ff вы получаете предполагаемое поведение.


Если вы хотите, вы можете создать steam из своих опций, а затем использовать функции потока, чтобы получить последний определенный элемент. (Или, скорее, последний определенный элемент перед неопределенным элементом)

def options[B](f: B => Option[B], initialValue: Option[B]): Stream[Option[B]] = {
  initialValue #:: options(f, initialValue.map(f(_)).flatten)
}

options.takeWhile(_.isDefined).last