Scala, эквивалентный генераторам Python?

Действительно ли возможно реализовать в 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 понятие. Продолжение в конечном счете превратит его в основной компилятор, и было бы возможно извлечь сложность в библиотеке?

Редактирование 2: проверьте ответ Rich в тот другой поток.

55
задан Community 23 May 2017 в 02:26
поделиться

3 ответа

Хотя питоновые генераторы - это круто, пытаться их дублировать - не лучший способ в Скале. Например, следующий код делает эквивалентную работу:

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

Результат может быть преобразован в Итератор простым вызовом . Итератор на результирующем Потоке:

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

Определение foo, используя Поток, будет выведено следующим образом:

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 у меня есть дефиниции, а потом я их просто конкатеную, как хочу.

Итак, хотя это и не ответ, я просто думаю, что вы лаете не на то дерево. Попытка написать Python на Scala неизбежно окажется непродуктивной. Работайте усерднее над идиомами Скалы, которые достигают тех же целей.

34
ответ дан 7 November 2019 в 07:30
поделиться

Для этого, я думаю, вам нужен плагин продолжений .

Наивная реализация (от руки, не составлена ​​/ проверяется):

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 захватывает контрольный поток от того, где он вызывается до конца Сброс блок, в котором он вызывается. Это передается как аргумент K в функцию Shift.

Итак, в приведенном выше примере, каждый придается (х) возвращает значение x (до этого значения) и сохраняет остальные вычисления в Состояние переменная. Он приводится из-за пределов Hasnext и методами .

Иди нежно, это, очевидно, ужасный способ реализации этого. Но это лучше всего, я мог бы сделать поздно ночью без компилятора удобного.

3
ответ дан 7 November 2019 в 07:30
поделиться

Другим решением на основе плагина на базе, на этот раз с более или менее инкапсулированным типом генератора,

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)
      }
    }
  }
}
11
ответ дан 7 November 2019 в 07:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: