【问题标题】:Scala equivalent to Python generators?Scala 相当于 Python 生成器?
【发布时间】:2011-01-09 09:44:25
【问题描述】:

是否可以在 Scala 中实现与 Python yield 语句等效的功能,在该语句中,它会记住使用它的函数的本地状态并在每次调用时“生成”下一个值?

我想要有这样的东西来将递归函数转换为迭代器。有点像这样:

# this is python
def foo(i):
  yield i
  if i > 0:
    for j in foo(i - 1):
      yield j

for i in foo(5):
  print i

除了,foo 可能更复杂,并通过一些非循环对象图递归。

其他编辑: 让我添加一个更复杂的示例(但仍然很简单): 我可以编写一个简单的递归函数来打印东西:

// this is Scala
def printClass(clazz:Class[_], indent:String=""): Unit = {
  clazz match {
    case null =>
    case _ =>
      println(indent + clazz)
      printClass(clazz.getSuperclass, indent + "  ")
      for (c <- clazz.getInterfaces) {
        printClass(c, indent + "  ")
      }
  }
}

理想情况下,我希望有一个库,可以让我轻松更改一些语句并将其用作迭代器:

// this is not Scala
def yieldClass(clazz:Class[_]): Iterator[Class[_]] = {
  clazz match {
    case null =>
    case _ =>
      sudoYield clazz
      for (c <- yieldClass(clazz.getSuperclass)) sudoYield c
      for (c <- clazz.getInterfaces; d <- yieldClasss(c)) sudoYield d
  }
}

似乎延续允许这样做,但我只是不明白shift/reset 的概念。 continuation 最终会进入主编译器吗?是否有可能提取出库中的复杂性?

编辑 2: 在另一个线程中检查Rich's answer

【问题讨论】:

  • 很难想出一个无法通过标准技术实现的易于处理的示例。例如,我认为您的yieldClass 示例可以通过巧妙地使用Iterator.++ 来实现。但是,是的,我认为yieldClass 可以在移位/重置方面实现。我不知道它什么时候会在不需要插件的情况下进入编译器。我认为大多数复杂性都可以分解为“生成器库”。我认为 Rich Dougherty 的博客是这些小动物的最佳解释来源。
  • 您对 Iterator.++ 的看法是正确的。 gist.github.com/286682 有效。我得去看看 Rich 的博客。
  • 这是一个副本,但奇怪的是,我没有在“相关”中看到原件。
  • 抱歉 OT,但我盯着你的 sudoYield 看了一会儿,直到我意识到你可能指的是 pseudoYield。伪=假的或假装的; sudo = 超级用户做(一个 linux 命令)。看到这个:dictionary.reference.com/browse/pseudo 这个:xkcd.com/149

标签: python scala


【解决方案1】:

要以一般方式执行此操作,我认为您需要continuations plugin

一个简单的实现(手绘,未编译/检查):

def iterator = new {
  private[this] var done = false

  // Define your yielding state here
  // This generator yields: 3, 13, 0, 1, 3, 6, 26, 27
  private[this] var state: Unit=>Int = reset {
    var x = 3
    giveItUp(x)
    x += 10
    giveItUp(x)
    x = 0
    giveItUp(x)
    List(1,2,3).foreach { i => x += i; giveItUp(x) }
    x += 20
    giveItUp(x)
    x += 1
    done = true
    x
  }

  // Well, "yield" is a keyword, so how about giveItUp?
  private[this] def giveItUp(i: Int) = shift { k: (Unit=>Int) =>
    state = k
    i
  }

  def hasNext = !done
  def next = state()
}

发生的情况是,对shift 的任何调用都会捕获从调用它的位置到调用它的reset 块末尾的控制流。这将作为k 参数传递到shift功能。

因此,在上面的示例中,每个giveItUp(x) 都返回x 的值(直到那时),并将其余的计算保存在state 变量中。它由hasNextnext 方法从外部驱动。

温和一点,这显然是一种糟糕的实现方式。但我最好在没有编译器的情况下在深夜做。

【讨论】:

  • 我认为如果移位/重置生成一个流,则可能会创建一个库,因此每次调用都会返回到移位/重置。我想。有点。
  • 博客在我上面答案的链接中:blog.richdougherty.com/search/label/continuations
  • 我收到“错误:类型不匹配”,其中found: scala.runtime.StringAdd @scala.continuations.uncps @scala.continuations.cps[Int,Int]required: ? @scala.continuations.cps[?,(Unit) =&gt; Int] 在线private[this] var state: Unit=&gt;Int = reset {
【解决方案2】:

虽然 Python 生成器很酷,但在 Scala 中尝试复制它们确实不是最好的方法。例如,下面的代码可以完成你想要的工作:

def classStream(clazz: Class[_]): Stream[Class[_]] = clazz match {
  case null => Stream.empty
  case _ => (
    clazz 
    #:: classStream(clazz.getSuperclass) 
    #::: clazz.getInterfaces.toStream.flatMap(classStream) 
    #::: Stream.empty
  )
}

其中流是延迟生成的,因此在被请求之前它不会处理任何元素,您可以通过运行以下命令来验证:

def classStream(clazz: Class[_]): Stream[Class[_]] = clazz match {
  case null => Stream.empty
  case _ => (
    clazz 
    #:: { println(clazz.toString+": super"); classStream(clazz.getSuperclass) } 
    #::: { println(clazz.toString+": interfaces"); clazz.getInterfaces.toStream.flatMap(classStream) } 
    #::: Stream.empty
  )
}

只需在生成的Stream 上调用.iterator 即可将结果转换为Iterator

def classIterator(clazz: Class[_]): Iterator[Class[_]] = classStream(clazz).iterator

使用Streamfoo 定义将呈现如下:

scala> def foo(i: Int): Stream[Int] = i #:: (if (i > 0) foo(i - 1) else Stream.empty)
foo: (i: Int)Stream[Int]

scala> foo(5) foreach println
5
4
3
2
1
0

另一种选择是连接各种迭代器,注意不要预先计算它们。这是一个示例,还带有调试消息以帮助跟踪执行:

def yieldClass(clazz: Class[_]): Iterator[Class[_]] = clazz match {
  case null => println("empty"); Iterator.empty
  case _ =>
    def thisIterator = { println("self of "+clazz); Iterator(clazz) }
    def superIterator = { println("super of "+clazz); yieldClass(clazz.getSuperclass) }
    def interfacesIterator = { println("interfaces of "+clazz); clazz.getInterfaces.iterator flatMap yieldClass }
    thisIterator ++ superIterator ++ interfacesIterator
}

这与您的代码非常接近。我有定义,而不是sudoYield,然后我只是按照我的意愿连接它们。

所以,虽然这不是一个答案,但我只是认为你在这里找错了树。尝试在 Scala 中编写 Python 注定是徒劳的。在实现相同目标的 Scala 习语上更加努力。

【讨论】:

  • 谢谢,我认为 Stream 解决方案是我一直在寻找的,因为它会延迟评估。你是对的,我不想在 Scala 中编写 python,但是因为在我没有想到解决方案之前我从未使用过流。
  • 顺便问一下,#:: 和 #::: 运算符的 scaladoc 在哪里?我似乎在 scala.collection.immutable.Stream scaladoc 上看不到它。
  • @huynhjl StreamIterator 解决方案都是延迟评估的。至于这些运算符,它们只存在于 Scala 2.8 上。它们确实没有在明显的地方定义,因为它不起作用。我不记得 - 或找到 - 他们现在在哪里定义。如果您使用的是 Scala 2.7,则可以将它们替换为 Stream.consStream.concat
  • Stream(和类似的递归构造)的缺点是在 Scala 中使用它们很容易导致堆栈溢出——这就是蹦床吸引人的原因。 blog.richdougherty.com/2009/04/…
  • #:: 和 #::: 位于 Stream 伴随对象 (scala-lang.org/api/beta/scala/collection/immutable/Stream$.html) 的 scaladoc 中,但遗憾的是没有很好的文档记录。 #:: 执行 Stream.cons,#::: 执行 Stream.concat。
【解决方案3】:

另一个基于 continuations 插件的解决方案,这次使用或多或少封装的 Generator 类型,

import scala.continuations._
import scala.continuations.ControlContext._

object Test {

  def loopWhile(cond: =>Boolean)(body: =>(Unit @suspendable)): Unit @suspendable = {
    if (cond) {
      body
      loopWhile(cond)(body)
    } else ()
  }

  abstract class Generator[T] {
    var producerCont : (Unit => Unit) = null
    var consumerCont : (T => Unit) = null

    protected def body : Unit @suspendable

    reset {
      body
    }

    def generate(t : T) : Unit @suspendable =
      shift {
        (k : Unit => Unit) => {
          producerCont = k
          if (consumerCont != null)
            consumerCont(t)
        }
      }

    def next : T @suspendable =
      shift {
        (k : T => Unit) => {
          consumerCont = k
          if (producerCont != null)
            producerCont()
        }
      }
  }

  def main(args: Array[String]) {
    val g = new Generator[Int] {
      def body = {
        var i = 0
        loopWhile(i < 10) {
          generate(i)
          i += 1
        }
      }
    }

    reset {
      loopWhile(true) {
        println("Generated: "+g.next)
      }
    }
  }
}

【讨论】:

  • 谢谢你,迈尔斯。我期待着尝试这个。我需要花一些时间先设置延续插件...
  • 我能够编译并运行您的示例。我可能需要一些时间和一些文档才能修改和理解它。
  • 这有助于学习如何使用定界延续来做事,但这种特殊解决方案的缺点是调用站点必须经过 CPS 转换。 Rich Dougherty 和我在stackoverflow.com/questions/2201882/… 提出替代解决方案。
  • 是的,我同意 Rich's 是一个更好的解决方案……更直接。我的实际上是从使用移位/重置的对称协程编码中派生出来的,我认为这体现在你指出的尴尬中。
【解决方案4】:

Scala 的 for (e &lt;- Producer) f(e) 形式的 for 循环转换为 foreach 调用,而不是直接转换为 iterator / next

foreach 中,我们不需要线性化对象的创建并将它们放在一个位置,因为迭代器的next 需要它。消费者函数f 可以被多次插入,恰好在需要的地方(即创建对象的地方)。

这使得在 Scala 中使用 Traversable / foreach 实现生成器的用例变得简单而高效。


最初的 Foo 示例:

case class Countdown(start: Int) extends Traversable[Int] {
    def foreach[U](f: Int => U) {
        var j = start
        while (j >= 0) {f(j); j -= 1}
    }
}

for (i <- Countdown(5))  println(i)
// or equivalent:
Countdown(5) foreach println

初始printClass-example:

  // v1 (without indentation)

  case class ClassStructure(c: Class[_]) {
    def foreach[U](f: Class[_] => U) {
      if (c eq null) return
      f(c)
      ClassStructure(c.getSuperclass) foreach f
      c.getInterfaces foreach (ClassStructure(_) foreach f)
    }
  }

  for (c <- ClassStructure(<foo/>.getClass)) println(c)
  // or equivalent:
  ClassStructure(<foo/>.getClass) foreach println

或带有缩进:

  // v2 (with indentation)

  case class ClassWithIndent(c: Class[_], indent: String = "") {
    override def toString = indent + c
  }
  implicit def Class2WithIndent(c: Class[_]) = ClassWithIndent(c)

  case class ClassStructure(cwi: ClassWithIndent) {
    def foreach[U](f: ClassWithIndent => U) {
      if (cwi.c eq null) return
      f(cwi)
      ClassStructure(ClassWithIndent(cwi.c.getSuperclass, cwi.indent + "  ")) foreach f
      cwi.c.getInterfaces foreach (i => ClassStructure(ClassWithIndent(i, cwi.indent + "  ")) foreach f)
    }
  }

  for (c <- ClassStructure(<foo/>.getClass)) println(c)
  // or equivalent:
  ClassStructure(<foo/>.getClass) foreach println

输出:

class scala.xml.Elem
  class scala.xml.Node
    class scala.xml.NodeSeq
      class java.lang.Object
      interface scala.collection.immutable.Seq
        interface scala.collection.immutable.Iterable
          interface scala.collection.immutable.Traversable
            interface scala.collection.Traversable
              interface scala.collection.TraversableLike
                interface scala.collection.generic.HasNewBuilder
                interface scala.collection.generic.FilterMonadic
                interface scala.collection.TraversableOnce
                  interface scala.ScalaObject
                interface scala.ScalaObject
              interface scala.collection.generic.GenericTraversableTemplate
                interface scala.collection.generic.HasNewBuilder
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.collection.generic.GenericTraversableTemplate
              interface scala.collection.generic.HasNewBuilder
              interface scala.ScalaObject
            interface scala.collection.TraversableLike
              interface scala.collection.generic.HasNewBuilder
              interface scala.collection.generic.FilterMonadic
              interface scala.collection.TraversableOnce
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.Immutable
            interface scala.ScalaObject
          interface scala.collection.Iterable
            interface scala.collection.Traversable
              interface scala.collection.TraversableLike
                interface scala.collection.generic.HasNewBuilder
                interface scala.collection.generic.FilterMonadic
                interface scala.collection.TraversableOnce
                  interface scala.ScalaObject
                interface scala.ScalaObject
              interface scala.collection.generic.GenericTraversableTemplate
                interface scala.collection.generic.HasNewBuilder
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.collection.generic.GenericTraversableTemplate
              interface scala.collection.generic.HasNewBuilder
              interface scala.ScalaObject
            interface scala.collection.IterableLike
              interface scala.Equals
              interface scala.collection.TraversableLike
                interface scala.collection.generic.HasNewBuilder
                interface scala.collection.generic.FilterMonadic
                interface scala.collection.TraversableOnce
                  interface scala.ScalaObject
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.ScalaObject
          interface scala.collection.generic.GenericTraversableTemplate
            interface scala.collection.generic.HasNewBuilder
            interface scala.ScalaObject
          interface scala.collection.IterableLike
            interface scala.Equals
            interface scala.collection.TraversableLike
              interface scala.collection.generic.HasNewBuilder
              interface scala.collection.generic.FilterMonadic
              interface scala.collection.TraversableOnce
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.ScalaObject
          interface scala.ScalaObject
        interface scala.collection.Seq
          interface scala.PartialFunction
            interface scala.Function1
              interface scala.ScalaObject
            interface scala.ScalaObject
          interface scala.collection.Iterable
            interface scala.collection.Traversable
              interface scala.collection.TraversableLike
                interface scala.collection.generic.HasNewBuilder
                interface scala.collection.generic.FilterMonadic
                interface scala.collection.TraversableOnce
                  interface scala.ScalaObject
                interface scala.ScalaObject
              interface scala.collection.generic.GenericTraversableTemplate
                interface scala.collection.generic.HasNewBuilder
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.collection.generic.GenericTraversableTemplate
              interface scala.collection.generic.HasNewBuilder
              interface scala.ScalaObject
            interface scala.collection.IterableLike
              interface scala.Equals
              interface scala.collection.TraversableLike
                interface scala.collection.generic.HasNewBuilder
                interface scala.collection.generic.FilterMonadic
                interface scala.collection.TraversableOnce
                  interface scala.ScalaObject
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.ScalaObject
          interface scala.collection.generic.GenericTraversableTemplate
            interface scala.collection.generic.HasNewBuilder
            interface scala.ScalaObject
          interface scala.collection.SeqLike
            interface scala.collection.IterableLike
              interface scala.Equals
              interface scala.collection.TraversableLike
                interface scala.collection.generic.HasNewBuilder
                interface scala.collection.generic.FilterMonadic
                interface scala.collection.TraversableOnce
                  interface scala.ScalaObject
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.ScalaObject
          interface scala.ScalaObject
        interface scala.collection.generic.GenericTraversableTemplate
          interface scala.collection.generic.HasNewBuilder
          interface scala.ScalaObject
        interface scala.collection.SeqLike
          interface scala.collection.IterableLike
            interface scala.Equals
            interface scala.collection.TraversableLike
              interface scala.collection.generic.HasNewBuilder
              interface scala.collection.generic.FilterMonadic
              interface scala.collection.TraversableOnce
                interface scala.ScalaObject
              interface scala.ScalaObject
            interface scala.ScalaObject
          interface scala.ScalaObject
        interface scala.ScalaObject
      interface scala.collection.SeqLike
        interface scala.collection.IterableLike
          interface scala.Equals
          interface scala.collection.TraversableLike
            interface scala.collection.generic.HasNewBuilder
            interface scala.collection.generic.FilterMonadic
            interface scala.collection.TraversableOnce
              interface scala.ScalaObject
            interface scala.ScalaObject
          interface scala.ScalaObject
        interface scala.ScalaObject
      interface scala.xml.Equality
        interface scala.Equals
        interface scala.ScalaObject
      interface scala.ScalaObject
    interface scala.ScalaObject
  interface scala.ScalaObject
  interface java.io.Serializable

【讨论】:

    【解决方案5】:

    Dsl.scala 就是你要找的。​​p>

    假设您要创建一个随机数生成器。生成的数字应该存储在一个惰性求值的无限流中,可以借助我们内置的特定领域关键字Yield 来构建。

    import com.thoughtworks.dsl.keys.Yield
    def xorshiftRandomGenerator(seed: Int): Stream[Int] = {
      val tmp1 = seed ^ (seed << 13)
      val tmp2 = tmp1 ^ (tmp1 >>> 17)
      val tmp3 = tmp2 ^ (tmp2 << 5)
      !Yield(tmp3)
      xorshiftRandomGenerator(tmp3)
    }
    

    其他示例可以在Scaladoc中找到。

    【讨论】:

      猜你喜欢
      • 2017-03-13
      • 2012-05-26
      • 2012-03-11
      • 2011-09-17
      • 1970-01-01
      • 1970-01-01
      • 2011-09-18
      • 1970-01-01
      相关资源
      最近更新 更多