2016-02-22 4 views
9

Angenommen, wir haben eine seitenwirksame "Producer" -Funktion f:() => Option[T], die einen Some zurückgibt, wenn sie wiederholt aufgerufen wird bis zu einem zukünftigen Punkt, an dem sie für immer None zurückgeben wird. (Beispielsweise könnte eine umschlossene Java API, die null bei EOF produziert, diese Art von Verhalten haben).Drehen Sie eine Nebeneffekt-Funktion, die Option in einen Iterator zurückgibt

Ist es möglich, diese Funktion in so etwas wie ein TraversableOnce oder einem Iterator mit folgenden Einschränkungen wickeln:

  • Standard-Scala Bibliothek Konstrukte bevorzugt
  • Die Reihenfolge beliebig lang sein könnte und halten alle Werte , z.B in einem Stream nicht
  • Ebenso gibt wollte
  • Der sichtbare Benutzer Quellcode keine Möglichkeit der Stapelüberlauf sein muss, sollte nicht verwendet ein var
  • Thema Sicherheit nicht
  • erforderlich ist

Es gibt einige nützliche Methoden sind auf dem Iterator Objekt, aber nichts, was genau zu meinem Anwendungsfall passt. Irgendwelche Ideen willkommen!

Antwort

6

Dies funktioniert der Trick:

def wrap[T](f:() => Option[T]): Iterator[T] = { 
    Iterator.continually(f()).takeWhile(_.isDefined).flatten  
} 

REPL Test:

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) 
+0

Vielen Dank für eine ausgezeichnete und schnelle Antwort! – satyagraha

+0

Die letzte '.map (_. Get)' darf in neueren Versionen von Scala durch '.flatten' ersetzt werden, was eine implizite TraversableOnce CBF und den zugehörigen Iterator einführt. Ich denke, das entspricht immer noch meinen Kriterien. – satyagraha

+0

Sie haben Recht. Ich habe das vermisst, weil das Scaladoc für "Iterator" es nicht erwähnt. Scheint wie ein Scaladoc-Bug: 'flatten 'kommt von der Anreicherungsklasse' TraversableOnce.FlattenOps' und Anreicherungen sollen von scaladoc gehandhabt werden (und viele sind es). Ich habe meine Antwort aktualisiert. –

2

Etwas orthogonal, kann dieses Verhalten mit Koroutinen erreicht werden. Es gibt mindestens eine Bibliothek für die Scala, die Koroutinen ermöglicht, können Sie es hier finden: http://storm-enroute.com/coroutines/

Hier ist ein Beispiel für den Code ist, dass du schreiben zu bekommen, was Sie wollen:

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) 

Oder alternativ:

@ val cr = call(sideEffectingFunction()) 
cr: Coroutine.Frame[Int, Unit] = Coroutine.Frame<depth: 1, live: true> 
@ while(cr.resume) println(cr.value) 
-1888916682 
1135466162 
243385373 

Oder im Geist der vorherigen Antwort:

@ 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 

T Der Vorteil des Koroutinen-Ansatzes besteht darin, dass Sie einen veränderlichen Zustand zwischen den Aufrufen der zugrundeliegenden Nebeneffekte behalten können, die alle von der Außenwelt innerhalb einer Koroutine gut versteckt sind. Coroutinen können auch zusammengesetzt und aufgerufen werden.

Natürlich, Koroutinen geben Ihnen viel mehr Macht als nur Ihre Aufgabe zu arbeiten, so dass dies ein Overkill sein könnte, um sie nur dafür hinzuzufügen. Es ist jedoch eine handliche Technik, die Sie beachten sollten.

Verwandte Themen