2013-03-29 2 views
9

Я часто сталкиваюсь с шаблоном, поэтому мне было интересно, есть ли в библиотеке 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 
} 

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

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

+0

Это не в библиотеке, и вы делаете это правильно. –

+2

добавить '@ tailrec'! –

+1

Это почти ['unfold'] (http://daily-scala.blogspot.co.at/2009/09/unfoldleft-and-right.html).Но это не происходит в каких-либо библиотеках. – phg

ответ

2

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

Как указывает Калин-Андрей, батуты доступны в стандартной библиотеке, используя TailCalls object.

С первой ссылке:

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 
+0

Я не знал о батутах, поэтому спасибо за это. Я обнаружил, что текущая версия библиотеки Scala поддерживает их через ['TailCalls'] (http://www.scala-lang.org/api/current/index.html#scala.util.control.TailCalls$). Ваш ответ ближе всего к тому, что я хочу. Можете ли вы изменить его так, чтобы он использовал батуты из библиотеки Scala? Тогда я могу принять ваш ответ. –

+1

Ничего себе, я не знал, что они были добавлены в стандартную библиотеку. StackOverflow отлично, я отвечаю на вопрос и узнаю что-то новое в одно и то же время :-) – iain

1

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

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, вы получаете предполагаемое поведение.

+2

Я не думаю, что это более красиво, чем реализация, заданная апеллятором. Кроме того, если функция создает побочные эффекты, то это решение делает f (x) дважды, что может быть неприемлемым. – myyk

1

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

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

options.takeWhile(_.isDefined).last 
2

Как насчет:

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

UPDATE

Или даже аккуратнее ИМХО (только одного обхода):

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 (в противном случае бесконечный цикл будет возможно).

Смежные вопросы