Programación funcional y reactiva - Computación
Introducción a la programación reactiva
Programación reactiva
Reactiva: que produce una reacción
Acciones sobre elementos de la GUI
Aproximaciones
Programación reactiva
Manejo de datos
Enfoques
Programación reactiva
Functional reactive programming
Enfoques
Patrón Observer
Patrón Observer
Patrón Observer
😔Problema
Cliente
Tienda
Patrón Observer
😊Solución
Patrón Observer
🏢Estructura
Patrón Observer
🏢Estructura
Patrón Observer
🏢Estructura
Patrón Observer en Java
Observable
Lenguajes de programación
Observer
Interfaz
public interface Observer {
void update();
}
Observable
Interfaz
public interface Observable {
void addObserver(Observer o);
void deleteObserver(Observer o);
void notifyObservers();
}
EjemploObservable
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class EjemploObservable implements Observable{
Set<Observer> observersSet = new HashSet<>();
@Override
public void addObserver(Observer o) {
observersSet.add(o);
}
@Override
public void deleteObserver(Observer o) {
observersSet.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observersSet) {
observer.update();
}
}
}
Ejemplo1Observer
public class Ejemplo1Observer implements Observer {
@Override
public void update() {
System.out.println("Se ha llamada al ejemplo 1");
}
}Main
public class Main {
public static void main(String[] args) {
EjemploObservable observable = new EjemploObservable();
observable.addObserver(new Ejemplo1Observer());
observable.notifyObservers();
}
}Reactive Streams
Reactive Streams
Flujo de datos
Reactive Streams
Flujo de datos
Reactive Streams
Lenguaje de programación
https://reactivex.io
ReactiveX
An API for asynchronous programming with observables streams
Reactive Streams
Lenguaje de programación
Algunos programas tienen sus propias implementaciones para manejar reactive streams.
Por ejemplo: Java
Reactive Streams
Lenguaje de programación
En Scala
libraryDependencies ++= Seq( "io.reactivex" % "rxscala_2.13" % "0.27.0" )
ReactiveX en Scala
RxScala
Trabajar con RxScala
Instalar la dependencia de RxScala: RxScala está basado en RxJava, por lo que necesitas agregar la dependencia a tu archivo build.sbt:
libraryDependencies ++= Seq( "io.reactivex" % "rxscala_2.13" % "0.27.0" )
Características principales
Reactive Streams
Flujo de datos
Ejemplo Básico: Crear un observable
Observable que emite una secuencia de valores
import rx.lang.scala.Observable
object BasicExample extends App {
// Crear un Observable con una lista de valores
val observable = Observable.just(1, 2, 3, 4, 5)
// Suscribirse al Observable
observable.subscribe(
onNext = x => println(s"Received: $x"), // Procesar cada valor emitido
onError = e => println(s"Error: ${e.getMessage}"), // Manejar errores
onCompleted = () => println("Completed!") // Notificar cuando el flujo termine
)
}
Clase principal para manejar flujos de datos reactivos
Crea un Observable que emite una lista de valores
Activa el Observable y le indica qué hacer con los datos que emite
Transformar datos con map
Multiplicar cada valor emitido por 2
import rx.lang.scala.Observable
object MapExample extends App {
// Crear un Observable con valores
val observable = Observable.just(1, 2, 3, 4, 5)
// Aplicar transformación a cada valor
observable
.map(_ * 2) // Multiplica cada valor por 2
.subscribe(x => println(s"Transformed: $x"))
}
Filtrar valores con filter
Mostrar sólo los números mayores a 3.
import rx.lang.scala.Observable
object FilterExample extends App {
// Crear un Observable con valores
val observable = Observable.just(1, 2, 3, 4, 5)
// Filtrar valores mayores a 3
observable
.filter(_ > 3) // Filtrar números mayores a 3
.subscribe(x => println(s"Filtered: $x"))
}
Flujos Asíncronos con temporizadores
Emitir un valor cada segundo
import rx.lang.scala.Observable
// Proporciona utilidades para trabajar con intervalos de tiempo
import scala.concurrent.duration._
object IntervalExample extends App {
// Crear un Observable que emite valores cada segundo
val observable = Observable.interval(1.second).take(5) // Tomar 5 valores
// Suscribirse al flujo
observable.subscribe(
x => println(s"Tick: $x"), // Procesar cada emisión
)
// Mantener la aplicación en ejecución para observar los valores
Thread.sleep(6000)
}
Crea un Observable que emite valores (0, 1, 2, ...) con un intervalo de 1 segundo entre cada emisión
Combinar flujos con merge
Combinar dos flujos diferentes
import rx.lang.scala.Observable
object MergeExample extends App {
// Crear dos Observables
val observable1 = Observable.just("A", "B", "C")
val observable2 = Observable.just("1", "2", "3")
observable1.merge(observable2)
.subscribe(x => println(s"Received: $x"))
}
Manejo de errores
Continuar el flujo incluso si ocurre un error.
import rx.lang.scala.Observable
object ErrorHandlingExample extends App {
// Crear un Observable que puede generar un error
val observable = Observable.just(10, 5, 0, 4)
// Manejar errores en el flujo
observable
.map(x => 10 / x) // Esto generará un error al dividir por 0
.onErrorResumeNext(_ => Observable.just(-1)) // Continuar con un valor predeterminado
.subscribe(
x => println(s"Received: $x"),
e => println(s"Error: ${e.getMessage}"),
() => println("Stream completed")
)
}
Simulación de un sensor
Emitir valores aleatorios como si fueran datos de un sensor.
import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._
object SensorExample extends App {
// Crear un flujo de datos del sensor
val sensorStream = Observable.interval(1.second).map(_ => Random.between(20.0, 30.0))
// Procesar el flujo del sensor
sensorStream
.take(5) // Emitir solo 5 valores
.subscribe(
x => println(f"Sensor reading: $x%.2f°C"),
e => println(s"Error: ${e.getMessage}"),
() => println("Sensor stopped")
)
// Mantener la aplicación corriendo
Thread.sleep(6000)
}
Combinar datos de sensores con zip
Combinar lecturas de dos sensores en un flujo único
import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._
object ZipExample extends App {
// Crear flujos de dos sensores
val sensor1 = Observable.interval(1.second).map(_ => Random.between(20, 30))
val sensor2 = Observable.interval(1.second).map(_ => Random.between(30, 40))
// Combinar los flujos con zip
sensor1.zip(sensor2)
.take(5)
.subscribe(
onNext = { case (temp1, temp2) => println(s"Sensor1: $temp1°C, Sensor2: $temp2°C") },
onError = e => println(s"Error: ${e.getMessage}"),
onCompleted = () => println("Stream completed!")
)
// Mantener la aplicación corriendo
Thread.sleep(6000)
}
Combinar datos de sensores con zip
Combinar lecturas de dos sensores en un flujo único
import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._
object ZipExample extends App{
// Crear flujos de dos sensores
val sensor1 = Observable.interval(1.second).map(_ => Random.between(20, 30))
val sensor2 = Observable.interval(1.second).map(_ => Random.between(30, 40))
// Forma más "pura" de usar zipWith
val flujoCombinado = sensor1.zipWith(sensor2) { (t1, t2) =>
// Retornamos un String formateado
s"Lectura combinada: Sensor1=$t1 - Sensor2=$t2"
}
flujoCombinado.take(5).subscribe(mensaje => println(mensaje))
// Mantener la aplicación corriendo
Thread.sleep(6000)
}
Sistema de alertas
Generar una alerta si un valor supera un umbral.
import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._
object AlertExample extends App{
val observable = Observable.interval(1.second).map(_ => Random.between(20.0, 40.0))
observable
.take(10)
// 1. doOnNext: Efecto secundario. Imprime TODO lo que pasa sin modificar el flujo.
.doOnNext(temp => println(f"Temperature: $temp%.2f°C"))
// 2. filter: Aquí aplicamos la lógica de negocio. Solo pasan los mayores a 35.
.filter(temp => temp > 35)
// 3. map: Preparamos el mensaje de alerta (transformación).
.map(temp => f"🚨 ALERT! High temperature detected: $temp%.2f°C")
// 4. subscribe: Solo recibe el mensaje final de alerta listo para imprimir.
.subscribe(
msg => println(msg),
e => println(s"Error: ${e.getMessage}"),
() => println("Monitoring stopped")
)
Thread.sleep(12000)
}