J'ai regardé cette question mais je ne comprends toujours pas la différence entre les traits Iterable et Traversable. Quelqu'un peut-il expliquer?
Pour le dire simplement, les itérateurs gardent l'état, les traversables non.
Un Traversable
a une méthode abstraite: foreach
. Lorsque vous appelez foreach
, la collection alimentera la fonction passée tous les éléments qu'elle conserve, l'un après l'autre.
D'un autre côté, un Iterable
a comme méthode abstraite iterator
, qui retourne un Iterator
. Vous pouvez appeler next
sur un Iterator
pour obtenir l'élément suivant au moment de votre choix. Jusqu'à ce que vous le fassiez, il doit garder une trace de l'endroit où il se trouvait dans la collection et de la suite.
Considérez-le comme la différence entre souffler et sucer.
Lorsque vous avez appelé un Traversable
s foreach
, ou ses méthodes dérivées, il insufflera ses valeurs dans votre fonction une par une - afin qu'il ait le contrôle sur l'itération.
Cependant, avec le Iterator
renvoyé par un Iterable
, vous en extrayez les valeurs, en contrôlant vous-même quand passer à la suivante.
tl; drIterables
sont Traversables
qui peuvent produire des états Iterators
Tout d'abord, sachez que Iterable
est un sous-portrait de Traversable
.
Seconde,
Traversable
nécessite l'implémentation de la méthode foreach
, qui est utilisée par tout le reste.
Iterable
nécessite l'implémentation de la méthode iterator
, qui est utilisée par tout le reste.
Par exemple, l'implémentation de find
pour Traversable
utilise foreach
(via a pour la compréhension) et lève une exception BreakControl
pour arrêter l'itération une fois qu'un élément satisfaisant a été trouvé.
trait TravserableLike {
def find(p: A => Boolean): Option[A] = {
var result: Option[A] = None
breakable {
for (x <- this)
if (p(x)) { result = Some(x); break }
}
result
}
}
En revanche, la soustraction Iterable
remplace cette implémentation et appelle find
sur le Iterator
, ce qui arrête simplement l'itération une fois l'élément trouvé:
trait Iterable {
override /*TraversableLike*/ def find(p: A => Boolean): Option[A] =
iterator.find(p)
}
trait Iterator {
def find(p: A => Boolean): Option[A] = {
var res: Option[A] = None
while (res.isEmpty && hasNext) {
val e = next()
if (p(e)) res = Some(e)
}
res
}
}
Ce serait bien de ne pas lever d'exceptions pour l'itération Traversable
, mais c'est la seule façon d'itérer partiellement lorsque vous utilisez simplement foreach
.
D'un point de vue, Iterable
est le trait le plus exigeant/puissant, car vous pouvez facilement implémenter foreach
en utilisant iterator
, mais vous ne pouvez pas vraiment implémenter iterator
en utilisant foreach
.
En résumé, Iterable
fournit un moyen de suspendre, reprendre ou arrêter l'itération via un Iterator
avec état. Avec Traversable
, c'est tout ou rien (sans exceptions pour le contrôle de flux).
La plupart du temps, cela n'a pas d'importance et vous voudrez l'interface plus générale. Mais si vous avez besoin d'un contrôle plus personnalisé de l'itération, vous aurez besoin d'un Iterator
, que vous pouvez récupérer à partir d'un Iterable
.
La réponse de Daniel sonne bien. Laissez-moi voir si je peux le dire avec mes propres mots.
Un Iterable peut donc vous donner un itérateur, qui vous permet de parcourir les éléments un par un (en utilisant next ()), et de vous arrêter et de continuer comme bon vous semble. Pour ce faire, l'itérateur doit garder un "pointeur" interne sur la position de l'élément. Mais un Traversable vous donne la méthode, foreach, pour parcourir tous les éléments à la fois sans s'arrêter.
Quelque chose comme Range (1, 10) n'a besoin que de 2 entiers comme état Traversable. Mais Range (1, 10) en tant qu'itérable vous donne un itérateur qui doit utiliser 3 entiers pour l'état, dont l'un est un index.
Étant donné que Traversable propose également foldLeft, foldRight, son foreach doit traverser les éléments dans un ordre connu et fixe. Il est donc possible d'implémenter un itérateur pour un Traversable. Par exemple. def iterator = toList.iterator