2010-10-24 44 views
2

J'ai un itérateur (en fait un Source.getLines) qui lit un flux infini de données d'une URL. Parfois, l'itérateur lance un java.io.IOException en cas de problème de connexion. Dans de telles situations, je dois me reconnecter et redémarrer l'itérateur. Je veux que ce soit transparent afin que l'itérateur ressemble à un itérateur normal au consommateur, mais en dessous se redémarre lui-même si nécessaire.redémarre l'itérateur sur les exceptions dans Scala

Par exemple, je voudrais voir le comportement suivant:

scala> val iter = restartingIterator(() => new Iterator[Int]{ 
    var i = -1 
    def hasNext = { 
    if (this.i < 3) { 
     true 
    } else { 
     throw new IOException 
    } 
    } 
    def next = { 
    this.i += 1 
    i 
    } 
}) 
res0: ... 

scala> iter.take(6).toList 
res1: List[Int] = List(0, 1, 2, 3, 0, 1) 

J'ai une solution partielle à ce problème, mais il échouera sur certains cas d'angle (par exemple un IOException sur le premier élément après un redémarrage) et il est assez laid:

def restartingIterator[T](getIter:() => Iterator[T]) = new Iterator[T] { 
    var iter = getIter() 
    def hasNext = { 
    try { 
     iter.hasNext 
    } catch { 
     case e: IOException => { 
     this.iter = getIter() 
     iter.hasNext 
     } 
    } 
    } 
    def next = { 
    try { 
     iter.next 
    } catch { 
     case e: IOException => { 
     this.iter = getIter() 
     iter.next 
     } 
    } 
    } 
} 

je continue sentir comme il y a une meilleure solution à cela, peut-être une combinaison de Iterator.continually et util.control.Exception ou quelque chose comme ça, mais je ne pouvais pas comprendre un. Des idées?

+0

J'ai ajouté une solution avec 'continually' et' util.control.Exception' à ma réponse originale. – huynhjl

Répondre

4

Ceci est assez proche de votre version et en utilisant scala.util.control.Exception:

def restartingIterator[T](getIter:() => Iterator[T]) = new Iterator[T] { 
    import util.control.Exception.allCatch 
    private[this] var i = getIter() 
    private[this] def replace() = i = getIter() 
    def hasNext: Boolean = allCatch.opt(i.hasNext).getOrElse{replace(); hasNext} 
    def next(): T = allCatch.opt(i.next).getOrElse{replace(); next} 
} 

Pour une raison récursif ce n'est pas la queue mais qui peut être fixé à l'aide d'une version légèrement plus prolixe:

def restartingIterator2[T](getIter:() => Iterator[T]) = new Iterator[T] { 
    import util.control.Exception.allCatch 
    private[this] var i = getIter() 
    private[this] def replace() = i = getIter() 
    @annotation.tailrec def hasNext: Boolean = { 
    val v = allCatch.opt(i.hasNext) 
    if (v.isDefined) v.get else {replace(); hasNext} 
    } 
    @annotation.tailrec def next(): T = { 
    val v = allCatch.opt(i.next) 
    if (v.isDefined) v.get else {replace(); next} 
    } 
} 

Modifier: Il y a une solution avec util.control.Exception et Iterator.continually:

def restartingIterator[T](getIter:() => Iterator[T]) = { 
    import util.control.Exception.allCatch 
    var iter = getIter() 
    def f: T = allCatch.opt(iter.next).getOrElse{iter = getIter(); f} 
    Iterator.continually { f } 
} 
+0

Ouais, le rendre récursif résout le cas d'angle qui m'inquiétait un peu. J'imagine que je pourrais obtenir à peu près le même comportement en changeant le second "iter.hasNext" et "iter.next" dans ma solution à "this.hasNext" et "this.next" et en ajoutant les annotations talrec. J'espérais qu'il existait une solution plus simple basée sur la composition. – Steve

+0

Très cool. C'est exactement le genre de chose que je cherchais, merci! – Steve

+0

@ huynhji- Je suis un peu confus par les snippets if (v.isDefined) v.get else {replace(); next} et if (v.isDefined) v.get else {replace(); hasNext}. Ces deux lignes ne réinitialisent pas l'itérateur jusqu'au début en cas d'exception. J'essaie de comprendre comment cela passerait au-dessus de la partie qui a jeté l'exception et qui passe à l'élément suivant de la source sur laquelle elle se répète? –

2

Il y a une meilleure solution, le Iteratee:

http://apocalisp.wordpress.com/2010/10/17/scalaz-tutorial-enumeration-based-io-with-iteratees/

Voici par exemple un recenseur qui redémarre à la rencontre une exception. La boucle interne avance l'Iteratee, mais la fonction externe est toujours conservée sur l'original. Puisque Iteratee est une structure de données persistante, pour redémarrer, il suffit de rappeler la fonction.

Je passe le lecteur par son nom ici de sorte que r est essentiellement une fonction qui vous donne un nouveau lecteur (redémarré). En pratique, vous voudrez mettre plus d'espace entre les deux (fermez le lecteur existant sur l'exception).

+0

Article intéressant, mais il ne parle pas vraiment de la gestion des exceptions. Pouvez-vous nous expliquer comment utiliser les itérations scalaz pour gérer mon problème? – Steve

+0

J'ai regardé cela pendant 15 minutes, mais je ne peux toujours pas envelopper ma tête autour d'elle. Ce que je suppose signifie que ce n'est probablement pas bon pour moi d'écrire un tel code même si/quand je le découvre ... – Steve

+1

L'article l'explique. Le code dit essentiellement: Pour nourrir d'un lecteur à un Iteratee, vérifiez si c'est fait accepter l'entrée. Si c'est le cas, il suffit de le retourner. S'il attend plus d'entrée, il aura une fonction 'k' pour accepter l'entrée. Lisez une ligne du lecteur et attribuez-la à 's'. Si nous obtenons une exception, redémarrez l'ensemble de l'énumération. Si nous avons une ligne nulle, signalez à l'Iteratee que nous avons atteint EOF. Sinon, alimentez 's' en' k' et en boucle. – Apocalisp

1

est ici une réponse qui ne fonctionne pas, mais se sent comme il se doit:

def restartingIterator[T](getIter:() => Iterator[T]): Iterator[T] = { 
    new Traversable[T] { 
    def foreach[U](f: T => U): Unit = { 
     try { 
     for (item <- getIter()) { 
      f(item) 
     } 
     } catch { 
     case e: IOException => this.foreach(f) 
     } 
    } 
    }.toIterator 
} 

Je pense que cela décrit très clairement le flux de contrôle, ce qui est génial.

Ce code va jeter un StackOverflowError à Scala 2.8.0 à cause d'un bug in Traversable.toStream, mais même après le correctif pour ce bogue, ce code ne fonctionne toujours pas pour mon cas d'utilisation, car toIterator appels toStream, ce qui signifie qu'il stocker tous les éléments en mémoire.

J'aimerais pouvoir définir un Iterator en écrivant simplement une méthode foreach, mais il ne semble pas y avoir de moyen facile de le faire.