[F#] ¿Y para qué me sirven los functors y las monads?

El sábado pasado tuve la oportunidad de asistir a un meetup de programación funcional donde hubo dos charlas muy entretenidas: En una se habló de elm y todas sus bondades en el desarrollo font-end, una introducción a su sintaxis, a sus herramientas y ecosistema. Y en la segunda una introducción a Monads y Functors pasando, claro, por algo de teoría de categorías e intentando aterrizar estos conceptos en Scala. Ha sido sin duda un espacio genial que se prestó para discusiones muy interesantes, muchas preguntas y varias ideas! Personalmente la pasé muy bien y me gustó el hecho que al final se terminó construyendo conocimiento entre todos.

Mientras discutíamos sobre esas dos extrañas palabras e intentábamos llegar a un consenso alguien levantó la mano y dijo algo como: “Y bueno, ¿eso a mi para qué me sirve?” Yo intenté explicar su utilidad cómo ahora la entiendo (antes lo entendía diferente y tal vez mañana lo vea con otros ojos) pero decidí que esta vez voy a escribirlo para que no se me olvide.

Antes de empezar

Antes de continuar quiero aclarar que este no será otro articulo más intentando explicar que son las monads, en serio, no estaría bien como primer post, dejémoslo en que son burritos, solo por ahora. Aquí solo quiero plasmar para qué sirven con un ejemplo de código en F#, y ese “para qué sirven” se limitará a ver un par de funciones que definen a los functores y a las monads. Ni más ni menos.

Empezando

Lo más interesante del asunto es que, si vienes de lenguajes como C#, probablemente ya hayas usado functores y monads sin saberlo, y de paso ya conozcas su utilidad. Es el caso del IEnumerable<T> que implementa el functor a través del método de extensión Select, si, el Select es el map que define a los functores y el método SelectMany, que es el bind, o una de las propiedades de las monads. Hablando así de seguro sonarás interesante y tengas éxito con l@s chic@s pero, para no enredarme con términos matemáticos (si, sé que son importantes, pero los dejaremos de lado por hoy) pienso en lo que muchos llaman el monad x, monad y, como amplificadores de tipos, si, las Listas, el Maybe, el Either los veo como amplificadores de tipos. Que si vienes de C# lucen como los genéricos, y si has visto algo de Haskell verás todas estas instancias son polimórficas. Estos amplificadores de tipos, en palabras más simples, pueden ser vistos como contextos, envoltorios o cajas, y como sé que algunos entendemos más con dibujos aquí hay una excelente explicación de manera grafica (también disponible en español).

Composición

De pronto el panorama esté un poco más claro con los dibujitos de la explicación grafica, pero, si aún no se le ve la utilidad, me atrevería a decir que está en la composición y en la eliminación de mucho ruido, que al final nos lleva a un código hermoso.

Veamos ahora un ejemplo, supongamos que tenemos las siguientes funciones:

    // From bd or http service... No IO monad here, plz 😛
    let findInvoice id =
        if id % 2 = 0 then Some {tax = 1.5; price = 200.0; date = DateTimeOffset.UtcNow}
        else None 

    // Based on invoice month, get a money value
    let calculateSomethingWithInvoice invoice =
        if invoice.date.Month > 9 then invoice.price * invoice.tax
        else 0.
    
    // Based on a money value, calculate yet another tax... you know, more TAXES!
    let calcOtherTax (finalValue : float) =
        if finalValue > 0.0 then Some (600. / finalValue)
        else None

El ejemplo es sencillo y hace uso de otro amplificador, el Option.

De manera breve: Una función obtiene de una BD una factura, la factura puede o no existir, por lo que se retorna un Option (Some o None). Hay una función que calcula algo con esa factura, ojo, con la factura NO con un Option<Factura> y retorna un valor monetario. Finalmente hay una función que, a partir de un valor moneda calcula algún impuesto, pero como el calculo de este impuesto implica una división sobre el valor de entrada somos muy cuidadosos y retornamos un valor moneda amplificado esto es, algo como un Option<moneda>, es decir, si el valor de entrada es cero la salida será None.

A la hora de usar estas funciones podemos hacerlo à la imperativa, algo como:

    let invoice = findInvoice 1

    let moneyValue = 
        if invoice = None then None 
        else Some (calculateSomethingWithInvoice (invoice.Value))

O a un estilo más à la F#:

    let moneyValue = 
        (findInvoice 1)
        |> function 
        | Some i -> Some  (calculateSomethingWithInvoice i)
        | None -> None

Pero ahora que sabemos que los functores nos permiten hacer este map, sacando el valor amplificado, aplicar la función y volviéndolo a poner dentro del envoltorio, podemos usar algo como:

    let moneyValue = findInvoice 1 |> Option.map calculateSomethingWithInvoice

O podemos crear un operador para que se vea à la Haskell (pero no se puede usar $)

    let (<%>) = Option.map
    let moneyValue = calculateSomethingWithInvoice <%> findInvoice 1 

Ahora nos falta calcular el valor del impuesto. Esta función recibe el valor monetario PERO retorna un valor amplificado (Option<Moneda>) por lo que al usar el map del functor obtendríamos el option de un option (Option<Option<Moneda>>). Aquí es donde nos es útil el bind de las monads! Esta es un poco más enredada y, para ahorrarme un párrafo entero, es mejor volver a ver los dibujitos.

    let moneyValue = calculateSomethingWithInvoice <%> findInvoice 1 
    let taxValue = moneyValue |> Option.bind calcOtherTax

Igual, podemos crear un operador à la Haskell y hacerlo todo en una línea

    let (>>=) = Option.bind
    let tax = calcOtherTax >>= (calculateSomethingWithInvoice <%> findInvoice 1)

O, por qué no, podemos crear una nueva función usando estas tres, algo como:

 
    let getTaxByInvoiceId id = calcOtherTax >>= (calculateSomethingWithInvoice <%> findInvoice id)

Conclusión

  • Si estás en Bogotá: pendiente de este meetup!. Si no estás en Bogotá: busca uno en tu ciudad, seguro hay uno!
  • Tal vez ya habías usado functores y monads pero no lo sabías.
  • No hace falta estar en la Nasa para usarlos en tu código día a día.
  • Cuando tengas un tiempo revisa teoría de categorías para no decirles ni amplificadores de tipos ni monads a los tipos a los que hoy les decimos monads

Espero les sea de utilidad

Hasta el próximo post

Un comentario sobre “[F#] ¿Y para qué me sirven los functors y las monads?

Deja un comentario