2013-03-29 15 views
9

Często napotykam wzorzec, więc zastanawiałem się, czy w bibliotece Scala jest jakaś wygodna metoda.Powtarzające się wywołanie funkcji, dopóki nie zwróci żadnej Brak

Niech to będzie funkcja f: A => Option[B]. Chciałbym wykonać powtarzające się wywołanie do f zaczynając od x, f(f(f(x).get).get...), aż f zwraca None i zachować ostatnią wartość inną niż None.

pisałem implementację dla tego:

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

Czy to już wdrożony w bibliotece standardowej?

Przykład użycia może dotyczyć listy (Zipper), która zachowuje aktualną pozycję. Wywołanie: next, None jest zwracana, jeśli nie ma elementów po bieżącej pozycji lub Option dla tej samej listy, ale z aktualną pozycją zwiększaną. Za pomocą powyższej metody można skonstruować metodę, która szuka listy do końca.

+0

Nie ma go w bibliotece i robisz to we właściwy sposób. –

+2

dodaj '@ tailrec'! –

+1

To jest prawie ['unfold'] (http://daily-scala.blogspot.co.at/2009/09/unfoldleft-and-right.html).Ale wydaje się, że nie występuje w żadnych bibliotekach. – phg

Odpowiedz

2

Co robisz wygląda bardzo specyficznego rodzaju trampoline. Bardziej ogólny przypadek używa funkcji owiniętych klasami przypadków zamiast Option i obsługuje różne typy argumentów i zwracanych.

Jak zauważa Calin-Andrei, trampoliny są dostępne w standardowej bibliotece przy użyciu TailCalls object.

Od pierwszego linku:

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 
} 

teraz z biblioteki standardowej

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

Nie wiedziałem o trampolinach, więc dziękuję za to. Odkryłem, że obecna wersja biblioteki Scala obsługuje je poprzez ['TailCalls'] (http://www.scala-lang.org/api/current/index.html#scala.util.control.TailCalls$). Twoja odpowiedź jest najbliższa temu, co chcę. Czy możesz zmodyfikować go tak, aby używał trampolin z biblioteki Scala? Wtedy mogę zaakceptować twoją odpowiedź. –

+1

Wow Nie wiedziałem, że zostały dodane do standardowej biblioteki. StackOverflow jest świetny, odpowiadam na pytanie i uczę się czegoś nowego w tym samym czasie :-) – iain

1

W przypadku konkursu piękności można zbudować funkcję, która przekształca istniejącą w potwora, o którym mówiliście za pomocą curry.

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)_ 

Teraz nazywając ff uzyskać zamierzone zachowanie.

+2

Nie sądzę, że jest to coś piękniejszego niż implementacja podana przez pytającego. Ponadto, jeśli funkcja wywołuje skutki uboczne, to rozwiązanie wykonuje dwukrotnie f (x), co może być niedopuszczalne. – myyk

1

Jeśli chcesz, możesz utworzyć parę z opcjami, a następnie użyć funkcji strumienia, aby uzyskać ostatni zdefiniowany element. (Czy raczej ostatnio zdefiniowany elementu przed elementem nieokreślonej)

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

Jak o:

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

UPDATE

Albo nawet neater IMHO (tylko pojedynczy przechodzenie):

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

Należy pamiętać, że jest to bezpieczne, aby zadzwonić collectFirst ponieważ nie możemy zacząć None (inaczej pętla nieskończona byłoby to możliwe).

Powiązane problemy