Scala equivalent aan Python-generatoren?

Is het mogelijk om in Scala iets te implementeren dat gelijk is aan de Python yield-instructie, waarbij het de lokale status van de functie onthoudt waar het wordt gebruikt en de volgende waarde “geeft” elke keer dat het wordt aangeroepen?

Ik wilde zoiets hebben om een ​​recursieve functie om te zetten in een iterator. Zoiets als dit:

# 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

Behalve, fookan complexer zijn en terugkeert via een acyclische objectgrafiek.

Aanvullende bewerking:
Laat me een complexer voorbeeld toevoegen (maar nog steeds eenvoudig):
Ik kan een eenvoudige recursieve functie schrijven die dingen gaandeweg afdrukt:

// 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 + "  ")
      }
  }
}

Idealiter zou ik graag een bibliotheek hebben waarmee ik gemakkelijk een paar uitspraken kan wijzigen en deze als een Iterator kan laten werken:

// 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
  }
}

Het lijkt erop dat voortzettingen dat mogelijk maken, maar ik begrijp het concept shift/resetgewoon niet. Zal voortzetting uiteindelijk in de hoofdcompiler komen en zou het mogelijk zijn om de complexiteit in een bibliotheek eruit te halen?

Bewerken 2:
check Rich’s antwoordin die andere thread.


Antwoord 1, autoriteit 100%

Hoewel Python-generatoren cool zijn, is het niet de beste manier om ze te dupliceren in Scala. De volgende code doet bijvoorbeeld hetzelfde werk als wat u wilt:

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

Hierin wordt de stream lui gegenereerd, dus het zal geen van de elementen verwerken totdat hierom wordt gevraagd, wat je kunt verifiëren door dit uit te voeren:

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
  )
}

Het resultaat kan eenvoudig worden omgezet in een Iteratordoor .iteratoraan te roepen in de resulterende Stream:

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

De foo-definitie, die Streamgebruikt, wordt als volgt weergegeven:

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

Een ander alternatief is het aaneenschakelen van de verschillende iterators en ervoor zorgen dat ze niet vooraf worden berekend. Hier is een voorbeeld, ook met foutopsporingsberichten om de uitvoering te traceren:

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
}

Dit komt redelijk in de buurt van uw code. In plaats van sudoYieldheb ik definities, en dan voeg ik ze gewoon samen zoals ik wil.

Dus, hoewel dit een non-antwoord is, denk ik dat je hier tegen de verkeerde boom blaft. Proberen om Python in Scala te schrijven zal ongetwijfeld onproductief zijn. Werk harder aan de Scala-idiomen die dezelfde doelen bereiken.


Antwoord 2, autoriteit 35%

Nog een vervolgoplossing op basis van plug-ins, dit keer met een min of meer ingekapseld Generator-type,

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)
      }
    }
  }
}

Antwoord 3, autoriteit 12%

Om dit in het algemeen te doen, denk ik dat je de continuations-plug-innodig hebt .

Een naïeve implementatie (uit de vrije hand, niet gecompileerd/gecontroleerd):

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()
}

Wat er gebeurt, is dat elke aanroep naar shiftde controlestroom vastlegt van waar het wordt aangeroepen tot het einde van het reset-blok waarin het wordt aangeroepen. Dit is doorgegeven als het argument kin de shift-functie.

Dus in het bovenstaande voorbeeld retourneert elke giveItUp(x)de waarde van x(tot dat moment) en slaat de rest van de berekening op in de statevariabele. Het wordt van buitenaf aangestuurd door de methoden hasNexten next.

Doe voorzichtig, dit is natuurlijk een vreselijke manier om dit te implementeren. Maar het beste kon ik ‘s avonds laat doen zonder een compiler bij de hand.


Antwoord 4, autoriteit 9%

Scala’s for-loop van de vorm for (e <- Producer) f(e)vertaalt zich in een foreach-aanroep, en niet direct in iterator/ next.

In de foreachhoeven we de creaties van objecten niet te lineariseren en ze op één plaats te hebben, zoals dat nodig zou zijn voor de nextvan de iterator. De consumentenfunctie fkan meerdere keren worden ingevoegd, precies waar het nodig is (d.w.z. waar een object wordt gemaakt).

Dit maakt de implementatie van use-cases voor generatoren eenvoudig en efficiënt met Traversable/ foreachin Scala.


Het eerste Foo-voorbeeld:

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

Het eerste printClass-voorbeeld:

 // 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

Of met inspringing:

 // 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

Uitvoer:

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

Antwoord 5

Dsl.scalais wat u zoekt.

Stel dat u een generator voor willekeurige getallen wilt maken. De gegenereerde getallen moeten worden opgeslagen in een traag geëvalueerde oneindige stroom, die kan worden gebouwd met behulp van ons ingebouwde domeinspecifieke trefwoord 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)
}

Andere voorbeelden zijn te vinden in de Scaladoc.

Other episodes