将副作用函数返回到Iterator中

时间:2016-02-22 10:04:14

标签: scala scala-collections

假设我们有一个副作用“生成器”函数f: () => Option[T],它在重复调用时返回Some,直到它将永远返回None的未来点。 (例如,在EOF处生成null的包装Java API可能会有这种行为。)

是否可以将此函数包装为TraversableOnceIterator,具有以下约束:

  • 标准Scala库构造首选
  • 序列可以任意长并保持所有值,例如在Stream中不想要
  • 同样,不得有堆栈溢出的可能性
  • 可见的用户源代码不应使用var
  • 不需要线程安全

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

2 个答案:

答案 0 :(得分:6)

这就是诀窍:

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)

答案 1 :(得分:2)

有点正交,这种行为可以使用协程来实现。至少有一个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

协程方法的优点是你可以在底层副作用函数的调用之间保持一个可变状态,所有这些都很好地隐藏在协同程序内部的外部世界中。协同程序也可以组合并相互调用。

当然,协同程序为您提供的功能远远超过让您的任务正常工作,因此为此添加它们可能会有些过分。但是,这是一个值得注意的方便技术。