Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recurrent call to a function until it returns None

I often encounter a pattern so I was wondering if there is any convenient method in Scala library for it.

Let it be a function f: A => Option[B]. I would like to do a recurrent call to f beginning with a starting x, f(f(f(x).get).get...), until f returns None and keep the last non-None value.

I wrote an implementation for this:

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

Is this already implemented in the standard library?

A usage example for this could be for a list (Zipper) which keeps the current position. By calling next, None is returned if there are no elements after the current position or an Option for the same list, but with current position incremented. By using the above method, an end method can be constructed that seeks the list to the end.

like image 785
Calin-Andrei Burloiu Avatar asked Mar 29 '13 15:03

Calin-Andrei Burloiu


4 Answers

What you are doing looks like a very specific type of trampoline. The more general case uses functions wrapped in case classes instead of an Option and supports different argument and return types.

As Calin-Andrei points out trampolines are available in the standard library using the TailCalls object.

From the first link:

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
}

Now with the standard library

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
like image 193
iain Avatar answered Nov 16 '22 04:11

iain


How about:

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

UPDATE

Or even neater IMHO (only single traversal):

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

Note that it is safe to call collectFirst because we cannot start with a None (otherwise an infinite loop would be possible).

like image 31
gzm0 Avatar answered Nov 16 '22 02:11

gzm0


in case of a beauty contest, you can build a function that transforms an existing one into the monster you were talking about through the use of currying.

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

Now calling ff you get the intended behaviour.

like image 25
Radu Stoenescu Avatar answered Nov 16 '22 04:11

Radu Stoenescu


If you want you could create a steam from you options and then use stream functions on it to get the last defined element. (Or rather the last defined element before the undefined element)

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

options.takeWhile(_.isDefined).last
like image 25
myyk Avatar answered Nov 16 '22 02:11

myyk