2016-02-22 33 views
9

Załóżmy, że mamy funkcję "producent", która jest efektem ubocznym, f:() => Option[T], która zwraca Some, gdy jest wielokrotnie wywoływana, aż do przyszłego momentu, w którym zawsze będzie zwracana None. (np. zawinięty Java API wytwarzający null w EOF może mieć takie zachowanie).Włącz funkcję powodującą efekt boczny, zwracając opcję do Iteratora.

Czy to możliwe, aby otoczyć tę funkcję w coś jak TraversableOnce lub Iterator, z następującymi ograniczeniami:

  • Standardowy Scala biblioteki konstruktów preferowanych
  • Sekwencja może być dowolnie długo i trzyma wszystkie wartości np w Stream nie chciał
  • Podobnie nie może być możliwość przepełnienia stosu
  • Widoczna kod źródłowy użytkownik nie powinien używać var
  • bezpieczeństwo Temat nie jest wymagane

Istnieje kilka przydatnych metod na obiekcie Iterator, ale nic, co dokładnie pasuje do mojego przypadku użycia. Wszelkie pomysły mile widziane!

Odpowiedz

6

To załatwia sprawę:

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

Test 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

Wielkie dzięki za doskonałą i szybką odpowiedź! – satyagraha

+0

Ostateczny '.map (_. Get)' może w ostatnich wersjach Scala zostać zastąpiony przez '.flatten', który wprowadza niejawny TraversableOnce CBF i powiązany iterator. Myślę, że to nadal spełnia moje kryteria. – satyagraha

+0

Masz rację. Tęskniłem za tym, ponieważ skaladoc dla "Iteratora" nie wspomina o tym. Wygląda jak błąd skaladoc: 'flatten' pochodzi z klasy enrichement' TraversableOnce.FlattenOps' i enrichements mają być obsługiwane przez skaladoc (i wiele z nich). Zaktualizowałem swoją odpowiedź. –

2

Nieco prostopadle, to zachowanie można osiągnąć stosując współprogram. Istnieje co najmniej jedna biblioteka dla Scala, który umożliwia współprogram, można go znaleźć tutaj: http://storm-enroute.com/coroutines/

Oto przykład kodu będziesz pisać to, co chcesz:

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) 

Lub alternatywnie:

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

Albo, w duchu poprzedniej odpowiedzi:

@ 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 Zaletą podejścia typu coroutines jest to, że możesz zachować zmienny stan między inwokacjami funkcji leżących u podstaw funkcji, które są dobrze ukryte przed światem zewnętrznym wewnątrz korupcji. Coroutines można również komponować i nawzajem się nawzajem.

Oczywiście, Coroutines dają o wiele więcej mocy niż tylko wykonywanie zadania, więc może to być przesada, aby dodać je tylko do tego. Jednak jest to przydatna technika, aby być świadomym.