Un sorbo de TEA: introducción a Elm (II)

Una taza de TEA cargadita

Continuando con el ejemplo del contador del artículo anterior vamos a añadir una nueva características al proyecto. Queremos que el usuario pueda decidir en cuánto se incrementa el contador y para ello tendrá dos opciones:

  • Entrar el valor de incremento deseado en un campo numérico.
  • Seleccionar un checkbox que active que el incremento sea aleatorio.

Estas nuevas características nos servirán como excusa para introducir varios conceptos importantes. Para presentarlos partiremos del proyecto Ellie del artículo anterior. Te invito a que lo abras y vayas añadiendo / modificando según vayamos avanzando.

En este artículo desarrollaremos el primer punto:

Entrar el valor de incremento

Para que el usuario pueda entrar el valor de incremento tendremos que hacer lo siguiente:

  • Añadir el campo numérico en la vista.
  • Disponer de un nuevo atributo en el modelo para albergar el valor de incremento.
  • Disponer de un nuevo tipo de mensaje para poder recoger los cambios en el nuevo campo numérico.

Incremento

En la parte del modelo es fácil satisfacer las nuevas necesidades, simplemente añadimos un nuevo atributo incremento al record userRecord.

Disponiendo de incremento ya podemos modificar la función de update para que lo utilice.

En este punto ya podemos compilar y jugar con el valor de incremento para incrementar el contador con diferentes valores. Nada complicado.

Campo numérico

El siguiente paso es crear un campo numérico y usarlo para modificar incremento. Para ello vamos a tener que crear un nuevo mensaje que será enviado desde el campo input y que transportará el valor numérico que hayamos entrado. La parte de la vista quedaría así:

Cosas a destacar de esta nueva parte del código:

  • input es una función del módulo Html, como div o button, y recibe los mismos parámetros: lista de atributos y lista de hijos (vacía en este caso).
  • Como type es una palabra reservada en Elm la función para el atributo type lleva un underscore: type_.
  • La función del atributo value, que al igual que _type forma parte del módulo Html.attributes, espera un String y por eso hacemos la conversión del valor numérico de incremento, que es de tipo Int, con toString
    toString es una función que forma parte de la librería estándar de Elm (no hace falta importarla) y sirve para convertir a String cualquier valor. Recibe un único parámetro que es lo que queremos convertir a cadena de texto.
  • onInput, del módulo Html.Events, es un event handler que envía el valor del campo al que pertenece como parámetro de un mensaje. Es decir, al cambiar el valor del input por ejemplo a 5 se enviará el mensaje CambiarIncremento con un payload de 5 a la función de Update. 
    Veamos cómo se declara un mensaje parametrizado.

Declaración del mensaje parametrizado

Como se puede ver se declara con el nombre del mensaje, un espacio y luego tantos tipos como el mensaje requiera separados por espacios. 
Otro ejemplo de mensaje sería:

MensajeCon5Parametros Int String Float Usuario Pedido

Los tipos soportados como parámetro de un mensaje son todos los primitivos como String, Int, Float,Bool, etc.. y otra clase de tipos personalizados (definidos por nosotros) llamados type alias.

Record types

Un record type es la declaración del tipo de un record. Es decir, nos permite declarar de forma explícita los tipos que componen el record.

Como se puede ver a continuación la sintaxis es muy parecida a la de un record.

type alias Usuario = 
{ id : Int
, nombre : String
, email : String
}

Para instanciarlo haríamos como si Usuario fuera una función y sus atributos sus parámetros:

john = Usuario 1 "John Doe" "me@john.com"

Una vez tenemos la instancia podemos operar con él como si fuera un record.

john.nombre // "John Doe"
{ john | nombre = "John Moe" } // Usuario 1 "John Moe" "me@john.com"

Ahora veamos cómo se utilizan los mensajes parametrizados en la función de Update.

Uso del mensaje parametrizado

En esta parte hemos introducido un par de cosas: el mensaje parametrizado y el tipo Result.

Como ya vimos en el artículo anterior los mensajes sirven para invocar la función de Update apuntando a una rama concreta del case statement.

En el caso de un mensaje con parámetros, como es el caso de CambiarIncremento, funciona exactamente de la misma forma pero con el añadido que la rama a la que apuntamos recibe tantos argumentos como parámetros tenga el mensaje, en este caso solo uno, incr, que es el valor numérico (que nos viene como String) del campo input que lanzó CambiarIncremento.

Tipo Result

Result es un módulo de la librería estándar de Elm (no hace falta importarlo). En este módulo, entre otras cosas, hay definido el type Result que sirve para representar valores de computaciones que podrían fallar.

Un Result puede ser o bien Ok o bien Err y ambos son tipos parametrizados. El type en si tiene esta forma:
(no es exactamente así pero por ahora ya nos sirve)

type Result
= Ok value
| Err error

Si es Ok devuelve el valor del resultado, si es Err devuelve un mensaje de error.

Hay muchas funciones standard de Elm que retornan un Result, básicamente cualquier función cuya operación pueda falla. Esta clase de tipos contenedor es lo que le permite a Elm ser un lenguaje sin excepciones en tiempo de ejecución porque, como veremos a continuación, nos obligan a tratar obligatoriamente todos los casos en las operaciones potencialmente peligrosas.

No Runtime Exceptions!

Ok, pero a la hora de usar el resultado a nosotros no nos interesa trabajar con Result directamente, queremos lo que lleva dentro, el valor (o error) que contiene. ¿Cómo lo hacemos? Pues bien, existen dos formas. Una es sacando el valor con un case statement mediante pattern matching.
(proyecto Ellie de ejemplo)

resultado = 
case String.toInt "22" of
Err msg ->
0
Ok num ->
num + 1
-- Resultado contiene 23 porque `String.toInt` "22" devuelve `Ok 22`

Aquí es importante remarcar que no puedes dejar de tratar el caso Err porque tienes prisa o porque se te ha olvidado, el compilador te obliga a cubrir todos los casos haciendo así que tu programa sea más robusto.

La otra forma de sacar el valor del contenedor Result es utilizando la función withDefault, que es la forma que estamos utilizando en el ejemplo por ser menos verbosa pero que viene a hacer lo mismo.
La función recibe dos parámetros, el primero es el valor por defecto que devolverá en caso de Err y el segundo es un Result.
Si el Result es un Ok la función devolverá el valor que transporta Ok, si es un Err entonces la función devolverá el valor por defecto que le hemos pasado.

Recapitulando

Hemos aprendido:

  • Como declarar y utilizar mensajes parametrizados.
  • Qué son los record types.
  • Como ejecutar código Elm desde el DOM a través de event handlers.
  • Como hacer conversiones entre tipos de forma segura.
  • Qué son los tipos contenedor como Result y cómo extraer su valor.

Código

Si te ha dado pereza escribir todo el código aquí presentado he creado este proyecto Ellie, por si quieres jugar un poco con él.

https://ellie-app.com/bnm8dnWj8a1/0

¿Qué más?

En el siguiente artículo implementaremos la segunda característica de la que hablábamos al principio

  • Seleccionar un checkbox que active que el incremento sea aleatorio.

Con esta nueva característica utilizaremos la arquitectura Elm en un modo extendido introduciendo los famosos efectos a través de subscripciones y comandos. La cosa se pondrá interesante :)

Social

Puedes mantenerte en contacto conmigo y enterarte cuando publique nuevos artículos a través de mis perfiles: