2016-02-22 8 views
9

Supponiamo di avere una funzione "produttore" che ha effetto collaterale f:() => Option[T] che restituisce un Some quando viene chiamato più volte fino a un punto futuro in cui restituirà sempre None. (ad esempio, un'API Java avvolta che produce null in EOF potrebbe avere questo tipo di comportamento).Attivare una funzione di effetto collaterale che restituisce un'opzione in un Iterator

E 'possibile avvolgere questa funzione in qualcosa come un TraversableOnce o un Iterator, con i seguenti vincoli:

  • standard Scala biblioteca costrutti preferiti
  • La sequenza potrebbe essere arbitrariamente lungo e in possesso di tutti i valori , per esempio in un Stream non è voluto
  • Analogamente deve esserci alcuna possibilità di stack overflow
  • Il codice sorgente utente visibile non si dovrebbe usare un filo di sicurezza var
  • non è necessaria

Ci sono alcuni metodi utili sull'oggetto Iterator, ma nulla che corrisponda esattamente al mio caso d'uso. Qualche idea benvenuta!

risposta

6

Questo fa il trucco:

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

prova REPL:

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

Mille grazie per una risposta eccellente e tempestiva! – satyagraha

+0

L'ultimo file '.map (_. Get)' può essere sostituito da '.flatten' nelle versioni recenti di Scala, che introduce un implicito TraitableOnce CBF e un iteratore associato. Penso che questo soddisfi ancora i miei criteri. – satyagraha

+0

Hai ragione. Mi è mancato perché lo scaladoc per 'Iterator' non lo menziona. Sembra un errore scaladoc: 'flatten' deriva dalla classe di enrichement' TraversableOnce.FlattenOps' e le distribuzioni dovrebbero essere gestite da scaladoc (e molti lo sono). Ho aggiornato la mia risposta. –

2

po ortogonalmente, questo comportamento può essere realizzato utilizzando coroutines. C'è almeno una libreria per Scala che consente coroutine, si può trovare qui: http://storm-enroute.com/coroutines/

Ecco un esempio del codice potrai scrivere per ottenere ciò che si vuole:

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) 

O, in alternativa:

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

Oppure, nello spirito della risposta precedente:

@ 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 Il vantaggio dell'approccio delle coroutine è che è possibile mantenere uno stato mutabile tra le invocazioni delle funzioni di effetti collaterali sottostanti, tutte ben nascoste dal mondo esterno all'interno di una coroutine. Le coroutine possono anche essere composte e invocate l'un l'altra.

Ovviamente, le coroutine ti danno molta più energia del semplice compito di far funzionare il tuo compito, quindi questo potrebbe essere eccessivo per aggiungerli solo per questo. Tuttavia, è una pratica tecnica da prendere in considerazione.

Problemi correlati