2013-03-29 79 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,如果在當前位置之後沒有元素,或者Option對於同一個列表,但當前位置遞增,則返回None。通過使用上面的方法,可以構建一個搜索列表到最後的方法。

+0

這不是在圖書館,而是你正確的做法。 –

+2

加'@ tailrec'! –

+1

這幾乎是['unfold'](http://daily-scala.blogspot.co.at/2009/09/unfoldleft-and-right.html)。但它似乎並不出現在任何庫中。 – phg

回答

2

你在做什麼看起來像一個非常特殊的trampoline。更一般的情況使用包裝在案例類中的函數而不是Option,並支持不同的參數和返回類型。

正如Calin-Andrei指出蹦牀在標準庫中使用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(否則無限循環將是可能的)開始。