[F#] Free monad interpreter, DSL y DDD

Hace ya poco más de dos años leí éste post, pero, en aquel momento no le encontré tanta utilidad (más por ignorancia que por otra cosa) y dejé el tema ahí, como algo interesante que, por lo pronto, no usaría en F#. No fue si no hasta finales del año pasado, leyendo otro articulo o una pregunta en SO (no recuerdo) donde se mencionó el termino Free Monad Interpreter que vi relacionados varios conceptos y tuve uno de esos momentos mentales

happy excited shocked awesome surprised

He dudado en escribir esta entrada porque aún hay detalles que se escapan a mi entendimiento y experiencia, pero sé que al escribirlo lo entenderé, al menos, un poco mejor, así que aquí va.

Lenguaje Ubicuo

Siempre que se habla de DDD se habla del lenguaje ubicuo que, cómo Martin Fowler lo expone, “es el término que Eric Evans usa en DDD para la práctica de construir un lenguaje común y riguroso entre desarrolladores y usuarios.” (traducción propia). En mi experiencia personal, y hablando desde la perspectiva del código en lenguajes imperativos (C# en mi caso particular), la construcción de éste lenguaje se limita a la representación en clases y métodos bien nombrados, “nutriéndolo”, el modelo de dominio, para que no sea anémico y que, cuando el usuario diga algo en su lenguaje natural seamos capaces de mapearlo a ésta representación nuestra. Pero lo que veo ahora es que este lenguaje natural puede ser también el lenguaje en el que se representen los requerimientos en nuestro código. Podemos traducir este lenguaje ubicuo a un DSL o mini-lenguaje que nos permitirá no traducir si no casi transcribir los requerimientos en nuestro código.

Free Monads

De nuevo esa extraña palabra aparece, pero en esta ocasión, no está sola. Qué es un Free Monad, teóricamente hablando (teoría de categorías y demás), se escapa de mi conocimiento. Pero me he hecho una imagen practica basada, como raro, en blogs y respuestas en SO. “Un Free Foo pasa a ser la cosa más simple que satisface todas las leyes del ‘Foo‘. Es decir que satisface exactamente las leyes necesarias para ser un Foo y nada extra.”1 esto es, el Free Monad es un monad (cumple sus leyes), pero no realiza ningún calculo, definiendo así nada más los contextos a computar2  En esta parte de quien y cómo computa (con qué monad) estos contextos es, cómo yo lo veo, donde viene la parte de los interpretadores del titulo de esta entrada. Gabriel Gonzalez tiene un excelente articulo explicando precisamente por qué importan los Free Monads, inicia su entrada de manera triunfal (traducirlo, pienso, lo arruinaría):

Good programmers decompose data from the interpreter that processes that data. Compilers exemplify this approach, where they will typically represent the source code as an abstract syntax tree, and then pass that tree to one of many possible interpreters.

Entre las ventajas de esta separación está la posibilidad de crear varios interpretadores separando así las acciones puras de las impuras (IO) y facilitando así las pruebas a nuestro código.

Implementación en F#

Sabemos que en F# existe el concepto de computation expressions y hasta donde sé no hay un computation expression para los Free Monads. Mostrar aquí su implementación dese cero sería caer en el error de la mera traducción del articulo mencionado al inicio de esta entrada. Por lo que espero que el lector se tome su tiempo para entender lo que allí se expone pues ese mismo código será empleado para desarrollar mi ejemplo.

Supongamos ahora un caso de uso como el siguiente: En el registro de nuevos usuarios se debe validar que la contraseña tenga una longitud mayor a seis y que el usuario no exista. Si se cumplen estas condiciones se registra el usuario, de lo contrario se muestra un mensaje indicando el error.

Con esta información procedemos a crear los tipos correspondientes

type User = {
    username : string
    password : string
}

type Error = string

Ahora definimos las acciones que se detallan en el caso de uso, esto es, consultar si el usuario existe, crear un usuario y notificar al usuario:

type UseCase<'a> =
    | Notify of Error * 'a
    | UserExist of User * (bool ->  'a)
    | Register of User * 'a

let mapUseCase (f : 'a -> 'b) (useCase : UseCase<'a>) : UseCase<'b> =
    match useCase with        
        |  Register (user, value) -> Register (user, f value)
        |  UserExist (user, fn)  -> UserExist (user, f << fn) 
        | Notify (message, v) -> Notify (message, f v)

Puede surgir la inquietud de por qué la validación no hace parte de este conjunto de acciones. Si bien podemos agregarla y trabajar con ella esta validación no presentaría ningún cambio entre los interpretes que ya veremos. Esta validación puede hacer uso del Option o bien de una monad Result<success,error> pero no cambiará el cómo es interpretada (puro/IO).

En el código del Free Monad solo he renombrado el código del ejemplo del post mencionado:

type FreeUseCase<'a> =
    | Pure of 'a
    | Free of UseCase<FreeUseCase<'a>>

let rec bind (f : 'a -> FreeUseCase<'b>) (useCase : FreeUseCase<'a>) : FreeUseCase<'b> =
    match useCase with
        | Pure value -> f value
        | Free t -> Free (mapUseCase (bind f) t)

let liftF (useCase:UseCase<'a>) : FreeUseCase<'a> = Free (mapUseCase Pure useCase)

let (>>=) = fun useCase f -> bind f useCase

let (>>.) = fun t1 t2 -> t1 >>= fun _ -> t2

Las funciones “elevadas” que nos permitirán construir el DSL:

let register (user : User) : FreeUseCase<unit> = liftF (Register (user, ()))
let notify (message : Error) : FreeUseCase<unit> = liftF (Notify (message, ()))
let userExist (user : User) : FreeUseCase<bool> = liftF (UserExist (user, true |> (=)))

Con este código ya podemos traducir el requerimiento y quedaría algo como:

let createUser' user = 
    userExist user
    >>= fun exists -> 
        if exists then 
            validatePassword user.password 
            |> function
            | Some _ -> register user
            | None -> notify "invalid password"
        else
            notify "username already exists"

Aquí se hace notoria, en mi humilde opinión, una de las limitaciones de F# al no poder generalizar los monads (polimorfismo de orden superior) y poder aplicar el fmap entre estos.

Vemos que los nombres de las funciones se corresponden con las acciones indicadas por el usuario, pero puede resultar confuso tantos símbolos indicando el flujo del programa, para esto podemos crear un computation expression y expresarlo de manera imperativa, más similar al lenguaje ubicuo. El computation expression quedaría, de nuevo tomando el código del ejemplo anterior, algo como:

type UseCaseBuilder() =
    member x.Bind(term, f) = bind f term
    member x.Return(value) = Pure value
    member x.Combine(term1, term2) = term1 >>. term2
    member x.Zero() = Pure ()
    member x.Delay(f) = f()
let usecase = new UseCaseBuilder()

Y su uso:

let createUser user = usecase {
    let! exist = userExist user
    if (not exist) then   
        match (validatePassword user.password) with
        | Some _ -> do! register user
        | None -> do! notify "invalid password"
    else
        do! notify "username already exists"
}

Ahora bien, como decíamos en la definición de los free monad hace falta el monad que reduzca (compute) estas expresiones, para ello vienen los interpretadores. En nuestro caso usaremos dos, uno puro, usando la monad List y uno impuro, usando IO (printfn).

let rec interpretIO (useCase:FreeUseCase<'a>) : 'a =
    match useCase with
        | Free (Register (user, next)) -> 
            printfn "User: %s Pass: %s" user.username user.password
            interpretIO next
        | Free (UserExist (user, fn)) -> 
            // should be from DB, u know... 
            interpretIO (fn (pureExist user.username))
        | Free (Notify (error, next)) -> 
            printfn "Error: %s" error
            interpretIO next
        | Pure a -> a

let rec interpretPure (acc : string list) (useCase:FreeUseCase<'a>) : string list =    
    match useCase with
    | Free (Register (user, next)) -> 
         interpretPure ((sprintf "User: %s Pass: %s" user.username user.password) :: acc) next
        | Free (UserExist (user, fn)) -> 
            interpretPure acc (fn (pureExist user.username))
        | Free (Notify (error, next)) ->             
            interpretPure ((sprintf "Error: %s" error) :: acc) next
    | Pure a -> acc

Con esta flexibilidad podemos probar con el interprete puro, y hacer pruebas de integración con otro interprete…

> interpretPure [] (createUser {username = "nicolas"; password = "123456"});;
val it : string list = ["User: nicolas Pass: 123456"]
> interpretPure [] (createUser {username = "hugo"; password = "123456"});;
val it : string list = ["Error: username already exists"]
> interpretPure [] (createUser {username = "nicolas"; password = "123"});;
val it : string list = ["Error: invalid password"]

Haskell Bonus

En el mismo blog hay un ejemplo usando haskell, mucho más sencillo y limpio que el de F#. Yo hice un gist para mostrar su uso interactuando con otra monad.

Conclusiones y apuntes finales

  • Al ser F# un lenguaje funcional no puro, implementar técnicas como esta permite una separación que, si bien requiere atención por parte del programador, hace que se vea más clara la intención de dicha separación.
  • En esto de la construcción del DSL he intentado, sin mucho éxito, crear funciones y combinadores de la forma si – entonces – cuando que permitan una lectura del código como si fuera lenguaje natural. La mejor aproximación ha sido con el do notation/computation expression.
  • ¿Recuerdan CQS/CQRS? van bien de la mano. Aquí un post explicando cómo

El código completo de este post está en este gist

Referencias y enlaces de interés

1 Traducción de un fragmento de esta respuesta de Kmett en SO

2 Este hilo en general y esta respuesta en particular han sido usados para construir la definición que intenté presentar

What is the “Free Monad + Interpreter” pattern?

Puede ser que esto termine siendo una sobre ingeniería, claro esta. When would I want to use a Free Monad + Interpreter pattern?

Un post muy similar a este, con base de datos y todo

Anuncios
[F#] Free monad interpreter, DSL y DDD

[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

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

Azure Functions y múltiples archivos de script en F#

Nota 1: Realmente no tiene nada que ver con los Azure Functions pero como aquí se usan Scripts de F# fue la excusa perfecta para aprenderlo.

La semana pasada se anunció un nuevo release de Azure Functions donde destacan el soporte mejorado para F#

image

Y esta misma semana un colega se tomó el tiempo para escribir un excelente tutorial (en inglés) con su respectivo ejemplo de código, donde muestra cómo crear una Azure Function con F# desde cero.

Ayer me animé a migrar el proyecto que había inspirado la anterior entrada a esta nueva versión con soporte mejorado para F# y esto fue lo que aprendí.

En la versión anterior, como había comentado, tenía un fsproj que ahora debía moverse a archivos de script de F# (aka .fsx). Mover los archivos a la nueva carpeta y cambiarles la extensión es la parte fácil. Ahora que hay archivos de Script en lugar de namespaces es necesario cargar los scripts a reutilizar con la directiva #load (como lo haríamos en los Scripts de C#) y abrir los módulos que se necesiten… pero esto solo es en teoría… porque me costó un poco más.

Trabajar con múltiples archivos de Script de F#

Nota 2: Esto no es solo para los fsx, es en general de F#, pero con los namespaces en  los fsproj y con las ayuda de VS no me era claro y lo desconocía.

Suponiendo una estructura de archivos como la siguiente:

operaciones.fsx
tiposOps.fsx (carga operaciones.fsx)

Si el código en operaciones.fsx quedara así

module Basicas =
    let sumar x y = x + y

El código en tiposOps.fsx genera el error

image

¿Por qué? Pues esto me pasa por obviar paginas de la documentación. Resulta que hay dos tipos de módulos, los top-level y los locales. Los top-level pueden aparecer únicamente como la primera declaración en un archivo1 y no llevan el signo igual, signo que si llevan los locales o anidados. Cuando no se usa un modulo top-level todo el código dentro del archivo se agrupa en un modulo creado automáticamente con el mismo nombre del archivo, sin extensión y con la primera letra en mayúscula.

Solo puede haber una función Run

Nota 3: Esto si tiene que ver con Azure Functions

Solo se puede nombrar una función run, de lo contrario recibiremos el mensaje de error error AF002: Multiple methods named 'Run'. Consider renaming methods.

Conclusiones

Con esta información podemos concluir que:

  • El código en el archivo operaciones.fsx usa un modulo local y al intentar usarlo se debería hacer de la forma open Operaciones.Basicas o mejor, cambiar la declaración del modulo para que sea un top-level. En lo personal no me gusta usar el modulo que se crea automáticamente y prefiero hacerlo explicito.
  • La documentación está para leerla :c

Referencias y enlaces de interés

1 Modules, Microsoft Docs

Defining Modules VS.NET vs F# Interactive, SO

Interactive Programming with F#, Microsoft Docs

Should F# functions be placed in modules, classes, or another structure?, SO

Espero les sea de utilidad.

Hasta el próximo post.

Azure Functions y múltiples archivos de script en F#

[F#] Funciones recursivas a lápiz y papel

Si hay algo que me ha gustado de la programación funcional (en lo poco que llevo estudiándole) es poder razonar sobre las funciones que escribo como meras funciones matemáticas, nada de tener que pensar en mutaciones de estado de un objeto o efectos colaterales al invocar determinada función. Y si hay algo que me gusta de las matemáticas, y la algoritmia en particular, es llenar hojas enteras de números, formulas y dibujos para razonar, abstraer y entender distintos conceptos y problemas. Hay gente que usa algún software u hojas de calculo para esto, a mi me gusta el lápiz y el papel.

El concepto de función recursiva es un tema fundamental en cualquier texto de programación funcional (al menos en los que yo he visto siempre tocan el tema), y en la web podemos encontrar definiciones, y en MSDN la implementación de F#

Lo que quiero exponer aquí es cómo razonar sobre las funciones recursivas cómo si estuviéramos operando sobre una función matemática. ¿Por qué? porqué hasta hace muy poco me complicaba y terminaba acudiendo a algún trace/log.

I'm a sad panda

Ahora veamos un ejemplo de cómo se puede analizar una función recursiva. Dada la función maxlist del tipo 'a list -> a que retornará el elemento mayor de una lista.

    let rec maxlist = 
        function
        | []    -> failwith "Lista vacia"
        | [x]   -> x
        | x::xs -> max x (maxlist xs)

Siempre que se usa recursividad con listas se puede sacar provecho del pattern matching para escribir código más expresivo. Viendo la función podemos deducir que: Para una lista vacía se lanzará una excepción, para una lista con un elemento regresaremos ese único elemento, y, para una lista con más de un elemento se aplicará la función max para el head de la lista y el resultado de llamar, nuevamente, la función maxlist con el tail de la lista como argumento. Entonces, usando lápiz y papel, podemos resolver la ecuación:

 
maxlist [5;1;3;7]
= max 5 (maxlist [1;3;7])
= max 5 (max 1 (maxlist [3;7]))
= max 5 (max 1 (max 3 (maxlist [7]))) //maxlist de una lista con un elemento retorna dicho elemento
= max 5 (max 1 (max 3 7))
= max 5 (max 1 7)
= max 5 7
= 7

De esta forma, resolviendo las ecuaciones como si fueran funciones matemáticas, podemos resolver las funciones recursivas.

En el siguiente ejemplo usaremos tres funciones: halve que partirá una lista en dos (retornando una tupla con las dos listas). La función mergeordlist que ordenará dos listas cuyos elementos ya estén ordenados dentro de una nueva lista. Y, finalmente, la función msort que usando estas otras dos funciones ordenará los elementos de una lista.

    let halve xs = List.splitAt (List.length xs / 2) xs

    let rec mergeordlists xs ys = 
        match (xs, ys) with
        | ([], ys) -> ys
        | (xs, []) -> xs
        | (x::xs, y::ys) -> 
            if x <= y then x :: mergeordlists xs (y::ys)
            else y :: mergeordlists (x::xs) ys

    let rec msort  =
        function
        | [] -> []
        | [x] -> [x]
        | xs -> 
            let (ys, zs) = halve xs
            mergeordlists (msort ys) (msort zs)

¿Cómo podemos resolver la función msort para un determinado conjunto de datos? ¡Fácil! reemplazando valores y resolviendo las ecuaciones siguiendo el orden de los paréntesis

msort [3;1;5;2]
= halve [3;1;5;2] // ([3;1],[5;2]) primero se resuelve halve
--> mergeordlist (msort [3;1]) (msort [5;2]) // reemplazamos los valores y se llama de nuevo a msort
= halve [3;1] // ([3],[1]) 
--> mergeordlist (mergeordlist (msort [3]) (msort [1])) (msort [5;2])
= mergeordlist (mergeordlist [3] (msort [1])) (msort [5;2]) //llamar a msort con una lista de un elemento devuelve la lista "[3]"
= mergeordlist (mergeordlist [3] [1]) (msort [5;2])
= halve [5;2] // ([5], [2])
--> mergeordlist (mergeordlist [3] [1]) (mergeordlist (msort [5]) (msort [2]))
= mergeordlist (mergeordlist [3] [1]) (mergeordlist [5] (msort [2]))
= mergeordlist (mergeordlist [3] [1]) (mergeordlist [5] [2])
= mergeordlist [1;3] (mergeordlist [5] [2])
= mergeordlist [1;3] [2;5]
= [1;2;3;5]

Entender una función recursiva usando lápiz y papel es como comerse una naranja

Espero les sea de útilidad.

Hasta el próximo post.

[F#] Funciones recursivas a lápiz y papel

[F#] Composición de funciones y conversión eta

Las funciones en F# tiene asociatividad de izquierda a derecha, esto es, dada la función:

let f g h i = g h i

Será lo mismo que (g h) i. Aplicando parcialmente g h y pasando i como argumento a la función resultante.

Teniendo presente el concepto de asociatividad de funciones, entonces ¿qué es la composición de funciones? en matemáticas Wikipedia dice:

Dadas dos funciones, bajo ciertas condiciones podemos usar los valores de salida de una de ellas como valores de entrada para la otra., creando una nueva función.

Lo que sería, aplicar una función al resultado de otra -> f(x) -> g(x) ->. Lo podemos representar de la forma (g ∘ f)(x) = g(f(x)).

Haciendo el ejercicio en álgebra, podríamos ver algo como lo siguiente.

Dadas las funciones:
f(x) = 2x²+3x
g(x) = 5x-2

(f ∘ g)(x) = f(g(x))
(f ∘ g)(x) = f(5x-2) = 2(5x-2)² + 3(5x-2)

Para verlo más claro en el código veamos un ejemplo. Dadas las funciones:

not //(bool -> bool) 
esPar //(int -> bool)

Crear una función esImpar (int -> bool)

Un primer enfoque podría ser:

let esImpar x = not(esPar(x))

Nótese que haciendo uso de los paréntesis estamos cambiando la asociatividad y hacemos explicito que se avalúe primero la función esPar.

Con esto se ve ya un poco más claro, y menos difícil de entender el f(g(x)). Con lo que podríamos entonces escribir una función que nos ayude con dicha composición, algo como:

let compoc f g x = f(g(x))

compoc tendrá la firma f:('a -> 'b) -> g:('c -> 'a) -> x:'c -> 'b

Podríamos usarla así:

let esImpar x = compoc not esPar x

Y podríamos simplificarlo un poco más haciendo uso de la conversión eta (η-conversión), que, en resumen y como entiendo su aplicación: si el último argumento está en los dos lados de la expresión podemos removerlo. En nuestro caso, en la expresión esImpar x = compoc not esPar x. El último y único argumento es x, y aparece en los dos lados de la expresión, por lo que sería lo mismo escribir:

let esImpar = compoc not esPar

Teniendo claro que F# es un lenguaje “functional first” era de esperarse que exista ya un operador para la composición de funciones, así como en Haskell tenemos el “.“, en F# tenemos el >> (forward composition operator).

La definición de este operador es:

let (>>) f g x = g(f x)

Qué es muy similar a lo que teníamos, dadas dos funciones f y g calculamos f(x) y pasamos su resultado a g.

Haciendo uso de este operador podemos reescribir nuestra función así:

let esImpar = esPar >> not

¿Qué pasa si las funciones tienen más parámetros? Podemos usar este operador sin problema y dado que su prioridad es más baja que las otras funciones no debemos preocuparnos por agruparlas con paréntesis, a diferencia de nuestra primera implementación. De esta forma conseguimos código más conciso.

Espero les sea de utilidad.
hasta el próximo post.

[F#] Composición de funciones y conversión eta

[F#] Operadores simbólicos

Lo que más me gusta de la programación funcional es su proximidad con las matemáticas, es decir, con un estilo funcional la barrera entre el razonamiento matemático y el como lo plasmo en el código es mucho menor. Así, entendemos todo como funciones al estilo f(x) o g(f(x))) y empezamos a notar lo fácil que es llevar este razonamiento a funciones y funciones de orden superior. Sin embargo, en el mundo de las matemáticas tenemos una gran cantidad de operaciones representadas por símbolos, por ejemplo, el símbolo +, que representa la adición.

Dicha operación puede ser plasmada en el código de la forma: add a b siendo a y b tipos numéricos (no me preguntes sobre el cuerpo de la función:P). Pero resulta tedioso tener que sumar números de esta forma let sum = add 1 2, Por fortuna, tenemos una serie de operadores que nos permiten realizar estas operaciones de forma más sencilla (de una forma más matemática), facilitando, entre otras cosas, la legibilidad de nuestro código.

En le caso de F#, tenemos una gran cantidad de operadores built-in, pero además, ¡tenemos la posibilidad de crear nuevos! esto es, escribir funciones cuyo nombre sea un operador aritmético. Técnicamente cualquier secuencia de los símbolos: !%&*+-./<=>@^|?

Por ejemplo, para definir un operador simbólico para calcular el factorial de un numero:

let rec (!) x =
	if x <= 1 then 1
	else x * !(x - 1)

Con esto, podemos escribir código con un estilo un poco más matemático y elegante: > !10;;

O una función que compare el valor de dos strings ignorando el case:

let (===) x y =	           
            String.Equals(x, y, StringComparison.InvariantCultureIgnoreCase)

Cabe anotar que, al ser funciones, los operadores simbólicos pueden ser pasados como parámetros a funciones de orden superior, solo hay que usar los paréntesis alrededor del operador:

>List.map (!) [1..5];;
val it : int list = [1; 2; 6; 24; 120]

Notas finales

Me parece preciso aclarar que esto no es lo mismo que tenemos en C# con la sobrecarga de operadores.

Hay una forma de poder utilizar operadores Unicode, al estilo de: ∑, empleando la extensión Math Symbol.

Espero les sea de utilidad.

Hasta el próximo post.

[F#] Operadores simbólicos

Programación funcional y valores inmutables

Como usuario de un lenguaje imperativo se hace muy normal trabajar con variables mutables todo el tiempo, y pese a que en ocasiones no sepamos si es mutable o inmutable muchos compiladores no nos dan aviso de esto y nos permiten realizar “modificaciones” al valor  de una variable

La importancia de los valores sobre las variables que cambian su estado (como en la programación imperativa) es uno de los grandes aportes de la programación funcional, en los lenguajes funcionales no se trabaja el concepto de variable, en su lugar conocemos valores, y todo serán valores de ahora en mas, es decir si enlazamos un valor a un símbolo este no podrá ser cambiado (let simbolo = 10).

En F# por ejemplo, podemos probar este concepto de una manera muy sencilla:

let entero = 10
entero &lt;- 20

Este código arroja el error error FS0027: This value is not mutable aclarándonos por supuesto lo que sospechamos 🙂

Aunque en F# tenemos también la posibilidad de hacer uso de variables mutables con el uso de la palara clave mutable:

let mutable entero = 10
entero &lt;- 20

Pese a que es algo muy sencillo de entender cambia drásticamente el como escribimos programas y es el punto de entrada para entender las funciones como valores, las funciones como ciudadanos de primera clase, aparte de como testeamos lo que escribimos, como entendemos este código y como lo reautorizamos. Pero quiero dejar estos temas para próximos  artículos.

Hasta el próximo post.

Programación funcional y valores inmutables