将返回选项的副作用函数转换为迭代器
Turn a side-effecting function returning Option into an Iterator
假设我们有一个副作用 "producer" 函数 f: () => Option[T]
当重复调用时 return 是一个 Some
直到它永远 return None
。 (例如,包装的 Java API 在 EOF 处产生 null
很可能有这种行为)。
是否可以将此函数包装成 TraversableOnce
或 Iterator
之类的东西,并具有以下限制:
- 首选标准 Scala 库结构
- 序列可以是任意长的并且包含所有值,例如在
Stream
中不想要
- 同样不能有栈溢出的可能
- 可见用户源代码不应使用
var
- 不需要线程安全
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
协程方法的优点是您可以在底层副作用函数的调用之间保持可变状态,所有这些都很好地隐藏在协程中,不为外界所知。协程也可以相互组合和调用。
当然,协同程序给你的功能远不止让你的任务工作,所以仅仅为此添加它们可能有点矫枉过正。然而,这是一个需要注意的便利技术。
假设我们有一个副作用 "producer" 函数 f: () => Option[T]
当重复调用时 return 是一个 Some
直到它永远 return None
。 (例如,包装的 Java API 在 EOF 处产生 null
很可能有这种行为)。
是否可以将此函数包装成 TraversableOnce
或 Iterator
之类的东西,并具有以下限制:
- 首选标准 Scala 库结构
- 序列可以是任意长的并且包含所有值,例如在
Stream
中不想要 - 同样不能有栈溢出的可能
- 可见用户源代码不应使用
var
- 不需要线程安全
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
协程方法的优点是您可以在底层副作用函数的调用之间保持可变状态,所有这些都很好地隐藏在协程中,不为外界所知。协程也可以相互组合和调用。
当然,协同程序给你的功能远不止让你的任务工作,所以仅仅为此添加它们可能有点矫枉过正。然而,这是一个需要注意的便利技术。