Llega nuestra cuarta parte sobre programacion funcional. Ya vimos en una primera entrega los funtores, en la segunda hablamos de funtores aplicativos y en la tercera vimos las monadas. Hoy vamos a ver como abstraer nuestros programas gracias a todos estos conceptos

Una monada, aunque la definimos como un funtor aplicativo, tambien la podemos definir, como dice Juan Manuel Serrano, como una API para escribir programas imperativos

Veamoslo con un ejemplo sencillo, un programa que hace eco, es decir que recibe una entrada y repite esa entrada en la salida devolviendonos la entrada. Lo primero que hacemos cuando queremos abstraer la logica de una aplicacion es usar una interfaz

public interface IO {

  String read();

  void write(String msg);

}

Con esa interfaz podemos crear nuestra logica de aplicacion

public class Program {

  private final IO io;

  public Program(IO io) {
    this.io = io;
  }

  public String echo() {
    String read = io.read();
    io.write(read);
    return read;
  }
}

Ahora toca la implementacion de IO que lee de consola y escribe en consola

import java.util.Scanner;

public class ConsoleIO implements IO {

  @Override
  public String read() {
    Scanner scanner = new Scanner(System.in);
    return scanner.nextLine();
  }

  @Override
  public void write(String msg) {
    System.out.println(msg);
  }
}

Hasta aqui todo esta perfecto pero cuando queremos que nuestra implementacion sea asincrona usando por ejemplo Future empezamos a tener problemas

public class FutureIO implements IO {

  @Override
  public Future<String> read() {
    
  }

  @Override
  public Future<Void> write(String msg) {
    
  }
}

La opcion que tenemos es usar el metodo get de la clase Future lo que hace que el codigo sea bloqueante y deje de ser asincrono. Si no queremos hacer esto lo unico que nos queda es cambiar la interfaz IO para que devuelva Future y cambiar toda la logica de Program

Nuestra logica depende de nuestro interprete y eso hace que cada vez que se cambie el interprete haya que reescribir la logica, algo que no queremos

Veamos como resolver este problema usando lo aprendido en los ultimos posts

Reescribimos la interfaz IO de la siguiente forma

public interface IO<f> {

  __<f, String> read();

  __<f, Void> write(String msg);

}

Ahora nuestra interfaz recibe clases parametrizadas. Y podemos reescribir nuestra clase Program

public class Program<f> {

  private final IO<f> io;

  public Program(IO<f> io) {
    this.io = io;
  }

  public String echo() {
    String read = io.read();
    io.write(read);
    return read;
  }
}

Por desgracia este codigo no compila ya que io.read() no devuelve String. Podemos cambiar la clase asi

public class Program<f> {

  private final IO<f> io;

  public Program(IO<f> io) {
    this.io = io;
  }

  public String echo() {
    __<f, String> read = io.read();
    io.write(read);
    return read;
  }
}

Pero ahora tendremos errores de compilacion en la linea io.write(read) y return read. La unica solucion que tenemos es usar las monadas cambiando nuestra clase Program

public class Program<f> {

  private final IO<f> io;
  private final Monad<f> monad;

  public Program(IO<f> io, Monad<f> monad) {
    this.io = io;
    this.monad = monad;
  }

  public __<f, String> echo() {
    return monad.bind(io.read(),
        msg -> monad.bind(io.write(msg),
            a -> monad.point(msg)));
  }
}

Ya podemos usar nuestro codigo para cualquier tipo de clases siempre y cuando hayamos definido una monada para ese tipo. Como ejemplo dejo una implementacion de la monada para Future. Primero hacemos el wrapper para nuestro tipo

@HktConfig(withVisibility = Visibility.Same)
public class FutureT<t> implements __<FutureT, t> {

  public final Future<t> future;

  public FutureT(Future<t> future) {
    this.future = future;
  }
}

Y luego definimos la monada

public class MonadFutureP implements Monad<FutureT> {

  @Override
  public <A, B> __<FutureT, B> bind(__<FutureT, A> fa, Function<A, __<FutureT, B>> f) {
    try {
      Future<A> future = Hkt.asFutureT(fa).future;
      return f.apply(future == null ? null : future.get());
    } catch (InterruptedException | ExecutionException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public <A> __<FutureT, A> point(A a) {
    return new FutureT<>(new FutureTask<>(() -> a));
  }
}

Esta no es la mejor definicion ya que usamos el metodo get de Future en vez de comprobar si ya ha sido ejecutado, aun asi nos vale para ver como implementar la monada