Scala: ¿Cuál es la diferencia entre los rasgos Traversable e Iterable en las colecciones de Scala?


Respuestas:


121

En pocas palabras, los iteradores mantienen el estado, los transitables no.

Una Traversabletiene un método abstracto: foreach. Cuando llame foreach, la colección alimentará a la función pasada con todos los elementos que conserva, uno tras otro.

Por otro lado, an Iterabletiene como método abstracto iterator, que devuelve un Iterator. Puede llamar nexta un Iteratorpara obtener el siguiente elemento en el momento que elija. Hasta que lo haga, debe realizar un seguimiento de dónde estaba en la colección y qué sigue.


4
Pero se Iterableextiende Traversable, así que supongo que te refieres a Traversables que no son Iterables.
Robin Green

4
@RobinGreen Me refiero a que cumplir con la Traversableinterfaz no requiere mantener el estado, mientras que cumplir con la Iteratorinterfaz sí.
Daniel C. Sobral

10
Traversables que Iterableno mantienen ningún estado de iteración. Es lo Iteratorcreado y devuelto por el Iterableque mantiene el estado.
Graham Lea

1
Vale la pena señalar que a partir de 2.13 el rasgo Traversable está obsoleto. Para citar a Stefan Zeiger, "La abstracción Traversable no ha tenido su peso en la biblioteca actual y probablemente no resurja en el nuevo diseño. Todo lo que queremos hacer se puede expresar con Iterable".
Igor Urisman

226

Piense en ello como la diferencia entre soplar y chupar.

Cuando haya llamado a Traversables foreach, o sus métodos derivados, soplará sus valores en su función uno a la vez, por lo que tiene control sobre la iteración.

Con el Iteratordevuelto por un Iterablesin embargo, succiona los valores de él, controlando cuándo pasar al siguiente usted mismo.


49
La gente suele llamar a esto empujar y tirar en lugar de soplar y chupar , pero me gusta tu amplitud de miras.
Martijn

2
Nunca olvidaré esto cuando se me preguntó en mi próxima entrevista
thestephenstanton

23

tl; dr Iterables son Traversablesque pueden producir statefulIterators


Primero, sepa que Iterablees un sustrato de Traversable.

Segundo,

  • Traversablerequiere implementar el foreachmétodo, que es utilizado por todo lo demás.

  • Iterablerequiere implementar el iteratormétodo, que es utilizado por todo lo demás.

Por ejemplo, la implementación de findfor Traversableusa foreach(vía a para comprensión) y lanza una BreakControlexcepción para detener la iteración una vez que se ha encontrado un elemento satisfactorio.

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
  }
}

Por el contrario, la Iterableresta anula esta implementación y llama finda Iterator, que simplemente deja de iterar una vez que se encuentra el elemento:

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
  }
}

Sería bueno no lanzar excepciones para la Traversableiteración, pero esa es la única forma de iterar parcialmente cuando se usa solo foreach.

Desde una perspectiva, Iterablees el rasgo más exigente / poderoso, ya que puede implementar fácilmente foreachusando iterator, pero realmente no puede implementar iteratorusando foreach.


En resumen, Iterableproporciona una forma de pausar, reanudar o detener la iteración mediante un stateful Iterator. Con Traversable, es todo o nada (sin excepciones para el control de flujo).

La mayoría de las veces no importa y querrá una interfaz más general. Pero si alguna vez necesita un control más personalizado sobre la iteración, necesitará un Iterator, que puede recuperar de un Iterable.


1

La respuesta de Daniel suena bien. Déjame ver si puedo expresarlo con mis propias palabras.

Entonces, un Iterable puede darle un iterador, que le permite atravesar los elementos uno a la vez (usando next ()), y detenerse y avanzar cuando lo desee. Para hacer eso, el iterador necesita mantener un "puntero" interno a la posición del elemento. Pero un Traversable le brinda el método, foreach, para atravesar todos los elementos a la vez sin detenerse.

Algo como Range (1, 10) necesita tener solo 2 enteros como estado como Traversable. Pero Range (1, 10) como Iterable le brinda un iterador que necesita usar 3 enteros para el estado, uno de los cuales es un índice.

Teniendo en cuenta que Traversable también ofrece foldLeft, foldRight, su foreach necesita atravesar los elementos en un orden fijo y conocido. Por lo tanto, es posible implementar un iterador para un Traversable. Por ejemplo, def iterator = toList.iterator

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.