Scala Jet Streams

A veces ocurre que, al obtener el resultado de un método que devuelve una colección, esta puede tener un tamaño considerable y además el proceso de computar dicha colección es lento o muy costoso. Por otra parte, si luego no vamos a procesar todos los elementos o si la colección no es finita (series numéricas), no podemos emplear colecciones convencionales.

Supongamos que queremos implementar un sistema de información atmosférica en tiempo real. Podemos ver el proceso de consulta como el típico bucle infinito:


case class Weather(
  tempCelsius: Float, 
  humidity: Double)

def checkWeather(): Weather = {
  /* Some request to a web service 
   * or access some weather station device.
   * We'll mock it.
   */
   import scala.util.Random
   Weather(
     Random.nextFloat() * 100 % 40,
     Random.nextDouble() % 1)
}

type WeatherHandler[T] = Weather => T

val handler: WeatherHandler[Unit] = println

while(true){
  handler.apply(checkWeather())
}

O podemos asumir que el tiempo atmosférico es una colección infinita de mediciones en momentos concretos.


val weather: TraversableOnce[Weather] = ???

for (value <- weather) handler.apply(value)

De esta manera, también podríamos modificar dicha colección mediante un map para obtener solo las temperaturas, por ejemplo:

val extractTemperature: WeatherHandler[Float] = 
  _.tempCelsius

val onlyTemperatures: TraversableOnce[Float] = 
  weather.map(extractTemperature)

Veamos de que maneras podemos obtener dicho comportamiento.

Iterators

Una primera aproximación podría ser implementar el trait Iterator[+A]:

object weather extends Iterator[Weather]{

  def hasNext() = true

  def next(): Weather = checkWeather()

}

Dado que nuestro iterador no termina nunca, el valor de hasNext() es siempre true. Cada vez que invoquemos al método next() obtendremos un nuevo valor de tipo Weather (esto puede implicar realizar una llamada a cierto web service o invocar un método de una central metereológica).

Fácil. No obstante, también podemos usar otro tipo de colecciones de Scala denominado Stream…

Streams

Podemos denominar a los Streams como colecciones ordenadas cuyos valores se evaluan de manera ‘lazy’.
Si por ejemplo definimos la serie númerica que compone los números enteros tenemos que:

val integers: Stream[Int] = {

  def loop(v: Int): Stream[Int] = 
    v #:: loop(v + 1)

  loop(0)

}

Como podéis ver, el símbolo #:: sirve para añadir un elemento al principio de un Stream. Si ejecutamos:

integers.head
integers.take(2).toArray

obtendremos el primer elemento del Stream y los dos primeros.

Podemos aplicar sobre este stream todas las operaciones que aplican a las colecciones de Scala (Traversable):

integers
  .filter(_ % 2 == 0)
  .take(5)
  .toArray //Array(0, 2, 4, 6, 8)

integers
  .takeWhile(_ < 5)
  .toArray //Array(0, 1, 2, 3, 4)

Fijaos que hasta que no invocamos el método toArray, no se evaluan los streams resultantes de las transformaciones aplicadas (filter, take, …)

Si ahora lo aplicamos a nuestro servicio meteorológico:

val weather: Stream[Weather] = {

  def loop(): Stream[Weather] = 
    checkWeather() #:: loop

  loop

}

obtenemos un Stream en cuyo interior definimos el método loop() que define como se va a evaluar el Stream (invocando al método checkWeather()).

Y nada más. Así de sencillo.

¡Que tiemble Maldonado!

Anuncios

2 thoughts on “Scala Jet Streams

  1. Buen post! Conviene recordar que no es aconsejable usar val cuando se usa un Stream ya que eso captura la cabeza del Stream, impidiendo que el garbage collector libere la memoria de los elementos ya procesados. A menos que se necesite reusar el mismo Stream materializado, es mejor usar def.

    Le gusta a 1 persona

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s