【问题标题】:Turn a side-effecting function returning Option into an Iterator将返回 Option 的副作用函数转换为迭代器
【发布时间】:2016-02-22 19:34:31
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: scala scala-collections


    【解决方案1】:

    这就是诀窍:

    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)
    

    【讨论】:

    • 最后的.map(_.get) 在最近的Scala 版本中可能被.flatten 取代,它引入了一个隐式的TraversableOnce CBF 和相关的迭代器。我认为这仍然符合我的标准。
    • 你是对的。我错过了,因为 Iterator 的 scaladoc 没有提到它。似乎是一个 scaladoc 错误:flatten 来自丰富类 TraversableOnce.FlattenOps 并且丰富应该由 scaladoc 处理(很多都是)。我更新了我的答案。
    【解决方案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
    

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

    当然,协同程序给你的能力远不止让你的任务工作,所以仅仅为此添加它们可能是一种矫枉过正的做法。但是,要注意这是一种方便的技术。

    【讨论】:

      猜你喜欢
      • 2016-08-17
      • 2019-02-04
      • 1970-01-01
      • 1970-01-01
      • 2017-03-24
      • 2023-01-30
      • 2013-04-25
      • 2020-08-08
      • 2015-05-30
      相关资源
      最近更新 更多