Действительно ли возможно реализовать в 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 в тот другой поток.
Хотя питоновые генераторы - это круто, пытаться их дублировать - не лучший способ в Скале. Например, следующий код делает эквивалентную работу:
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 неизбежно окажется непродуктивной. Работайте усерднее над идиомами Скалы, которые достигают тех же целей.
Для этого, я думаю, вам нужен плагин продолжений .
Наивная реализация (от руки, не составлена / проверяется):
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
и методами
.
Иди нежно, это, очевидно, ужасный способ реализации этого. Но это лучше всего, я мог бы сделать поздно ночью без компилятора удобного.
Другим решением на основе плагина на базе, на этот раз с более или менее инкапсулированным типом генератора,
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)
}
}
}
}