将返回选项的副作用函数转换为迭代器

Turn a side-effecting function returning Option into an Iterator

假设我们有一个副作用 "producer" 函数 f: () => Option[T] 当重复调用时 return 是一个 Some 直到它永远 return None。 (例如,包装的 Java API 在 EOF 处产生 null 很可能有这种行为)。

是否可以将此函数包装成 TraversableOnceIterator 之类的东西,并具有以下限制:

Iterator 对象上有一些有用的方法,但没有与我的用例完全匹配的方法。欢迎任何想法!

这样就可以了:

def wrap[T](f: () => Option[T]): Iterator[T] = {
  Iterator.continually(f()).takeWhile(_.isDefined).flatten      
}

REPL 测试:

scala> :paste
// Entering paste mode (ctrl-D to finish)
var i = 0
def sideEffectingFunc(): Option[Int] = {
  i += 1
  if (i < 10) Some(i)
  else None
}
// Exiting paste mode, now interpreting.

i: Int = 0
sideEffectingFunc: ()Option[Int]

scala> val it = wrap(sideEffectingFunc)
it: Iterator[Int] = non-empty iterator

scala> it.toList
res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

有点正交,这种行为可以使用协程来实现。至少有一个支持协程的 Scala 库,您可以在这里找到它:http://storm-enroute.com/coroutines/

这是您为获得所需内容而编写的代码示例:

import org.coroutines._

def sideEffectingFunction = coroutine { () => 
  val limit = new scala.util.Random().nextInt(10)
  val seq = new scala.util.Random
  var counter = 0 // mutable state is preserved between coroutine invocations
  while (counter < limit) {
    counter += 1
    yieldval(seq.nextInt)
  }
}
defined function sideEffectingFunction

@ val cr = call(sideEffectingFunction()) 
cr: Coroutine.Frame[Int, Unit] = Coroutine.Frame<depth: 1, live: true>
@ cr.resume 
res31: Boolean = true
@ cr.value 
res32: Int = 57369026
@ cr.resume 
res33: Boolean = true
@ cr.value 
res34: Int = -1226825365
@ cr.resume 
res35: Boolean = true
@ cr.value 
res36: Int = 1304491970
@ cr.resume 
res37: Boolean = false
@ cr.value 
java.lang.RuntimeException: Coroutine has no value, because it did not yield.
  scala.sys.package$.error(package.scala:27)
  org.coroutines.Coroutine$Frame$mcI$sp.value$mcI$sp(Coroutine.scala:130)
  cmd38$.<init>(Main.scala:196)
  cmd38$.<clinit>(Main.scala:-1)

或者,或者:

@ val cr = call(sideEffectingFunction()) 
cr: Coroutine.Frame[Int, Unit] = Coroutine.Frame<depth: 1, live: true>
@ while(cr.resume) println(cr.value) 
-1888916682
1135466162
243385373

或者,本着之前答案的精神:

@ val cr = call(sideEffectingFunction()) 
cr: Coroutine.Frame[Int, Unit] = Coroutine.Frame<depth: 1, live: true>
@ cr.resume 
res60: Boolean = true
@ val iter = Iterator.continually(cr.value).takeWhile(_ => cr.resume) 
iter: Iterator[Int] = non-empty iterator
@ iter.foreach(println) 
1595200585
995591197
-433181225
220387254
201795229
754208294
-363238006

协程方法的优点是您可以在底层副作用函数的调用之间保持可变状态,所有这些都很好地隐藏在协程中,不为外界所知。协程也可以相互组合和调用。

当然,协同程序给你的功能远不止让你的任务工作,所以仅仅为此添加它们可能有点矫枉过正。然而,这是一个需要注意的便利技术。