Translate

Mostrando las entradas con la etiqueta elm. Mostrar todas las entradas
Mostrando las entradas con la etiqueta elm. Mostrar todas las entradas

viernes, 26 de septiembre de 2025

Simulando Listas por Comprensión en Elm


En lenguajes como Haskell o Python, las listas por comprensión permiten generar y transformar listas con una sintaxis muy compacta.

En Elm no existen listas por comprensión como sintaxis, pero sí podemos expresar lo mismo con funciones de orden superior como map, filter y concatMap.

En Haskell:


[x * 2 | x <- [1..5]]

-- Resultado: [2,4,6,8,10]


En Elm:


dobles : List Int

dobles =

    [1,2,3,4,5]

        |> List.map (\x -> x * 2)


Resultado: [2,4,6,8,10]


Con condición (filter)


En Haskell:


[x * 2 | x <- [1..5], x > 2]

-- Resultado: [6,8,10]


En Elm:


doblesMayoresQueDos : List Int

doblesMayoresQueDos =

    [1,2,3,4,5]

        |> List.filter (\x -> x > 2)

        |> List.map (\x -> x * 2)


-- Resultado: [6,8,10]


Generando pares (concatMap)


En Haskell:


[(x,y) | x <- [1,2], y <- [3,4]]

-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


En Elm:


pares : List (Int, Int)

pares =

    [1,2]

        |> List.concatMap (\x ->

            [3,4] |> List.map (\y -> (x, y))

        )


-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


Aunque Elm no tiene listas por comprensión como sintaxis, podemos lograr la misma expresividad combinando funciones de orden superior:

  • List.map → transformación.
  • List.filter → condiciones.
  • List.concatMap → anidación (equivalente a los múltiples generadores de Haskell).

De esta forma, Elm mantiene un estilo declarativo y expresivo, alineado con su filosofía de simplicidad y claridad.

Lazy Evaluation en Elm: ¿Existe?


En muchos lenguajes funcionales, como Haskell, la lazy evaluation (evaluación perezosa) es una característica central: los valores no se calculan hasta que realmente se necesitan.

En Elm, en cambio, la evaluación es estricta por defecto (eager evaluation), lo que significa que todos los argumentos de una función se evalúan inmediatamente antes de ser usados.


Veamos un ejemplo simple:


calcular : Int -> Int -> Int

calcular x y =

    x + 1


resultado =

    calcular 5 (Debug.log "evaluando..." 10)


Al ejecutar este código, veremos en la consola:


evaluando...


Aunque el parámetro y nunca se use, Elm lo evalúa igual, porque siempre evalúa sus argumentos antes de ejecutar la función.


Aunque Elm no es lazy por diseño, sí ofrece el módulo Lazy, que permite diferir la evaluación de expresiones costosas hasta que realmente las necesitemos.


import Lazy exposing (Lazy)


perezoso : Lazy Int

perezoso =

    Lazy.lazy (\_ -> 1 + 2)


resultado : Int

resultado =

    Lazy.force perezoso


En este caso:

  • Lazy.lazy encapsula un cálculo.
  • Lazy.force ejecuta el cálculo cuando es necesario.


¿Cuándo usar Lazy?


El módulo Lazy es útil en:

  • Estructuras grandes que no siempre se recorren por completo.
  • Cálculos costosos que solo se necesitan en ciertas condiciones.
  • Simulación de comportamiento perezoso, para optimizar rendimiento.


Sin embargo, Elm sigue favoreciendo la evaluación estricta en la mayoría de los casos, para mantener la simplicidad y la predictibilidad del lenguaje.

Pattern Matching en Elm: Desestructurando Datos de Forma Segura


El pattern matching en Elm es una herramienta poderosa para trabajar con listas, tuplas, registros y tipos algebraicos.

Permite desestructurar datos y cubrir todos los casos posibles de manera clara y segura, gracias al compilador.

Pattern Matching con listas:


primerElemento : List Int -> String

primerElemento lista =

    case lista of

        [] ->

            "Lista vacía"

        x :: xs ->

            "El primer elemento es " ++ String.fromInt x


[] → lista vacía.

x :: xs → descompone la lista en el primer elemento x y el resto xs.


Ejemplo:


primerElemento []          -- "Lista vacía"

primerElemento [10,20,30]  -- "El primer elemento es 10"


Pattern Matching con tuplas


sumaTupla : (Int, Int) -> Int

sumaTupla tupla =

    case tupla of

        (a, b) ->

            a + b


sumaTupla (3, 4)   -- 7


También podés combinar:


describeTupla : (String, Int) -> String

describeTupla (nombre, edad) =

    nombre ++ " tiene " ++ String.fromInt edad ++ " años"


Pattern Matching con registros


type alias Persona =

    { nombre : String

    , edad : Int

    }


saludo : Persona -> String

saludo persona =

    case persona of

        { nombre, edad } ->

            "Hola " ++ nombre ++ ", tenés " ++ String.fromInt edad ++ " años"


Ejemplo:

saludo { nombre = "Ana", edad = 30 }

-- "Hola Ana, tenés 30 años"


Pattern Matching con tipos algebraicos


Elm brilla con sus tipos definidos por el usuario.


type Resultado

    = Exito String

    | Error String


procesar : Resultado -> String

procesar res =

    case res of

        Exito mensaje ->

            "Todo salió bien: " ++ mensaje

        Error mensaje ->

            "Ocurrió un error: " ++ mensaje


Ejemplo:


procesar (Exito "Archivo guardado")

-- "Todo salió bien: Archivo guardado"


procesar (Error "Permiso denegado")

-- "Ocurrió un error: Permiso denegado"


Algo clave en Elm: el compilador exige cubrir todos los casos posibles en un case.

Si olvidás uno, el código no compila → esto evita errores en tiempo de ejecución.


  • case ... of permite desestructurar listas, tuplas, registros y tipos algebraicos.
  • Es una forma segura y declarativa de trabajar con datos.
  • El compilador obliga a cubrir todos los casos, garantizando que no haya estados no contemplados.


 El pattern matching es uno de los pilares que hace a Elm un lenguaje expresivo y confiable.


sábado, 20 de septiembre de 2025

Registros en Elm


En Elm, un registro es una colección de pares clave = valor, similar a un objeto en otros lenguajes, pero inmutable y tipado.

Son muy usados para agrupar datos de manera clara y segura.


persona : { nombre : String, edad : Int }

persona =

    { nombre = "Ana", edad = 30 }


  • nombre es de tipo String.
  • edad es de tipo Int.


nombreDeAna : String

nombreDeAna =

    persona.nombre

-- Resultado: "Ana"


También se puede usar una función anónima para acceder:


List.map .nombre [ persona, { nombre = "Luis", edad = 25 } ]

-- ["Ana", "Luis"]


Los registros en Elm son inmutables.

Para "cambiar" un campo, se crea una copia con el campo modificado:


personaMayor : { nombre : String, edad : Int }

personaMayor =

    { persona | edad = 31 }


Esto no altera persona, sino que genera un nuevo registro.

Las funciones pueden recibir registros directamente:


saludar : { nombre : String } -> String

saludar r =

    "Hola, " ++ r.nombre

saludar persona

-- "Hola, Ana"


Cuando un registro es usado en varios lugares, conviene definir un alias de tipo:


type alias Persona =

    { nombre : String

    , edad : Int

    }


juan : Persona

juan =

    { nombre = "Juan", edad = 40 }


cumplirAnios : Persona -> Persona

cumplirAnios p =

    { p | edad = p.edad + 1 }


Podemos "abrir" un registro en sus campos:


mostrar : Persona -> String

mostrar { nombre, edad } =

    nombre ++ " tiene " ++ String.fromInt edad ++ " años"

mostrar juan

-- "Juan tiene 40 años"


En Resumen:

  • Un registro agrupa datos con nombres de campo.
  • Son inmutables, para "modificarlos" se usa la sintaxis { r | campo = nuevoValor }.
  • Los alias de tipo (type alias) hacen el código más claro.
  • Se puede usar desestructuración para acceder fácilmente a los campos.


En Elm, los registros reemplazan el concepto de objetos de otros lenguajes, pero sin herencia ni mutabilidad.

martes, 16 de septiembre de 2025

Tipos Genéricos en Elm


En Elm, al igual que en muchos lenguajes funcionales, los tipos genéricos permiten escribir funciones y estructuras de datos que funcionan con diferentes tipos sin necesidad de duplicar código.

Un tipo genérico se indica con letras minúsculas como a, b, c, etc.

Veamos un ejemplo simple:


identity : a -> a

identity x =

    x


Aquí, la función identity recibe un valor de cualquier tipo a y lo devuelve.

  • Si le paso un Int, devuelve un Int.
  • Si le paso un String, devuelve un String.


identity 42

-- Resultado: 42

identity "hola"

-- Resultado: "hola"


Las listas en Elm también son genéricas.

Su tipo es List a, donde a puede ser cualquier tipo.


longitud : List a -> Int

longitud lista =

    List.length lista


longitud puede recibir:

longitud [1, 2, 3]          -- funciona con List Int

longitud ["a", "b", "c"]    -- funciona con List String


En ambos casos la función devuelve la cantidad de elementos, sin importar de qué tipo sean.


Las tuplas también soportan tipos genéricos:


swap : (a, b) -> (b, a)

swap (x, y) =

    (y, x)


Ejemplo de uso:


swap (1, "uno")

-- Resultado: ("uno", 1)


swap (True, 3.14)

-- Resultado: (3.14, True)


Podemos crear estructuras más complejas con varios genéricos:


maybeFirst : List a -> Maybe a

maybeFirst lista =

    case lista of

        [] ->

            Nothing


        x :: _ ->

            Just x


  • Si la lista está vacía, devuelve Nothing.
  • Si tiene elementos, devuelve Just el primero.


Funciona con cualquier tipo de lista:


maybeFirst [1, 2, 3]       -- Just 1

maybeFirst ["a", "b"]      -- Just "a"

maybeFirst []              -- Nothing


Los tipos genéricos se escriben con letras minúsculas (a, b, c).

  • Permiten que funciones y estructuras trabajen con cualquier tipo.
  • Están presentes en funciones (identity), listas (List a), tuplas ((a, b)), y en tipos como Maybe a.
  • Nos ayudan a escribir código más reutilizable y flexible.



domingo, 14 de septiembre de 2025

Lambdas en Elm


En Elm, las lambdas (o funciones anónimas) son una herramienta fundamental para escribir código conciso y expresivo. Una lambda es simplemente una función sin nombre, definida directamente en el lugar donde se necesita.

La sintaxis de una lambda es:

\param1 param2 -> expresion


Por ejemplo:

\x -> x + 1


Es una función que recibe un número x y devuelve x + 1.


Con más de un parámetro:

\a b -> a + b


Las lambdas son muy útiles cuando trabajamos con funciones como map, filter o fold:


List.map (\x -> x * 2) [1,2,3]

-- Resultado: [2,4,6]


List.filter (\x -> x > 5) [3,7,1,8]

-- Resultado: [7,8]


Las lambdas pueden capturar variables de su entorno, convirtiéndose en clausuras:


sumarN : Int -> List Int -> List Int

sumarN n lista =

    List.map (\x -> x + n) lista


sumarN 5 [1,2,3]

-- Resultado: [6,7,8]


Acá la lambda \x -> x + n recuerda el valor de n aunque se ejecute después.

En Elm, los operadores son funciones. Esto significa que:


(+) 3 4 -- 7

(<) 2 5 -- True


Y en lugar de escribir:

\a b -> a < b


Podés usar directamente:

(<)


Ejemplo:

List.sortWith (<) [3,1,2]

-- Resultado: [1,2,3]


Todas las funciones en Elm son curried. Esto significa que se pueden aplicar parcialmente:


(>) 10

-- Es equivalente a \x -> 10 > x


List.filter ((>) 5) [1,7,3,9]

-- Resultado: [1,3]


Las lambdas en Elm hacen que trabajar con funciones de orden superior sea simple y elegante. Entre la posibilidad de capturar variables (clausuras), usar operadores como funciones y aplicar parcialmente, Elm ofrece un estilo de programación muy expresivo y seguro.

jueves, 11 de septiembre de 2025

Listas en Elm: Una Guía Práctica


En Elm, las listas son una de las estructuras de datos más utilizadas. Son colecciones inmutables de elementos del mismo tipo, y ofrecen muchas funciones para trabajar con ellas de forma segura y declarativa.

Podés crear una lista escribiendo los elementos entre corchetes [] separados por comas:


numeros : List Int

numeros = [1, 2, 3, 4, 5]


palabras : List String

palabras = ["hola", "elm", "listas"]


Una lista vacía se define como:


vacia : List Int

vacia = []


Acceder al primer elemento


List.head [1,2,3] -- Just 1


 Acceder al resto de la lista


List.tail [1,2,3] -- Just [2,3]


Largo de la lista


List.length [1,2,3] -- 3


Concatenar listas


[1,2] ++ [3,4] -- [1,2,3,4]


Aplica una función a cada elemento:


List.map (\x -> x * 2) [1,2,3] -- [2,4,6]


Filtra elementos que cumplen una condición:


List.filter (\x -> x > 2) [1,2,3,4] -- [3,4]


Reduce una lista a un único valor:


List.foldl (+) 0 [1,2,3,4] -- 10


Elm ofrece el operador :: para construir listas agregando un elemento al inicio:


1 :: [2,3,4] -- [1,2,3,4]


"hola" :: ["elm", "listas"] -- ["hola","elm","listas"]


Ejemplo completo :


numeros : List Int

numeros = [1, 2, 3, 4, 5]


pares : List Int

pares = List.filter (\n -> modBy 2 n == 0) numeros


cuadrados : List Int

cuadrados = List.map (\n -> n * n) 


main =

    Debug.log "Pares" pares

    |> Debug.log "Cuadrados" cuadrados


Las listas en Elm son poderosas, seguras y fáciles de manipular gracias a la librería estándar. Una vez que domines map, filter y fold, vas a poder expresar la mayoría de las transformaciones sobre colecciones de forma clara y concisa.




domingo, 7 de septiembre de 2025

Comandos y suscripciones

 Anteriormente vimos cómo la arquitectura Elm gestiona las interacciones del ratón y el teclado, pero ¿qué ocurre con la comunicación con los servidores? ¿Con la generación de números aleatorios?

Para responder a estas preguntas, es útil comprender mejor cómo funciona la arquitectura Elm en segundo plano. Esto explicará por qué las cosas funcionan de forma ligeramente diferente a lenguajes como JavaScript, Python, etc.


Sandbox

No le he dado mucha importancia, pero hasta ahora todos nuestros programas se crearon con Browser.sandbox. Proporcionamos un modelo inicial y describimos cómo actualizarlo y visualizarlo.

Puedes imaginar Browser.sandbox como la configuración de un sistema como este:



Nos mantenemos en el mundo de Elm, escribiendo funciones y transformando datos. Esto se conecta al sistema de ejecución de Elm. Este sistema determina cómo renderizar HTML eficientemente. ¿Ha cambiado algo? ¿Cuál es la modificación mínima del DOM necesaria? También detecta cuándo alguien hace clic en un botón o escribe en un campo de texto. Lo convierte en un mensaje y lo introduce en el código de Elm.

Al separar claramente toda la manipulación del DOM, es posible utilizar optimizaciones extremadamente agresivas. Por lo tanto, el sistema de ejecución de Elm es una de las razones principales por las que Elm es una de las opciones más rápidas disponibles.


element

En los siguientes ejemplos, usaremos Browser.element para crear programas. Esto introducirá los conceptos de comandos y suscripciones que nos permiten interactuar con el mundo exterior.

Puedes imaginar Browser.element como la configuración de un sistema como este:


Además de generar valores HTML, nuestros programas también enviarán valores Cmd y Sub al sistema de ejecución. En este contexto, nuestros programas pueden ordenar al sistema de ejecución que realice una solicitud HTTP o genere un número aleatorio. También pueden suscribirse a la hora actual.

Creo que los comandos y las suscripciones cobran más sentido al ver ejemplos, por lo tanto, en los próximos posts veremos ejemplos. 



martes, 2 de septiembre de 2025

Informe de errores en Elm


Quizás tengamos un sitio web donde la gente introduzca su edad. Podríamos comprobar si la edad es razonable con una función como esta:

isReasonableAge : String -> Result String Int

isReasonableAge input =

  case String.toInt input of

    Nothing ->

      Err "That is not a number!"


    Just age ->

      if age < 0 then

        Err "Please try again after you are born."


      else if age > 135 then

        Err "Are you some kind of turtle?"


      else

        Ok age


-- isReasonableAge "abc" == Err ...

-- isReasonableAge "-13" == Err ...

-- isReasonableAge "24"  == Ok 24

-- isReasonableAge "150" == Err ...


No solo podemos comprobar la edad, sino que también podemos mostrar mensajes de error según los detalles de la entrada. ¡Este tipo de retroalimentación es mucho mejor que nada!

El tipo Result también puede ayudarte a recuperarte de errores. Esto se observa al realizar solicitudes HTTP. Supongamos que queremos mostrar el texto completo de Ana Karenina de León Tolstói. Nuestra solicitud HTTP genera una cadena de error de resultado para indicar que la solicitud puede tener éxito con el texto completo o puede fallar de diversas maneras:


type Error

= BadUrl String

| Timeout

| NetworkError

| BadStatus Int

| BadBody String


-- Ok "All happy ..." : Result Error String

-- Err Timeout : Result Error String

-- Err NetworkError : Result Error String

A partir de ahí, podemos mostrar mensajes de error más atractivos, como ya comentamos, pero también podemos intentar recuperarnos del fallo. Si vemos un Timeout, puede que funcione esperar un poco e intentarlo de nuevo. Mientras que si vemos un BadStatus 404, no tiene sentido volver a intentarlo.


Result de Elm


El tipo Maybe puede ser útil con funciones simples que podrían fallar, pero no indica el motivo. Imagina que un compilador simplemente indicara "Nothing" si algo fallaba en tu programa. ¡Buena suerte intentando averiguar qué falló!

Aquí es donde el tipo Result resulta útil. Se define así:


type Result error value

  = Ok value

  | Err error


El objetivo de este tipo es proporcionar información adicional cuando algo falla. Es muy útil para el informe y la recuperación de errores.


jueves, 28 de agosto de 2025

Maybe en Elm


 Seguimos con el Maybe

Este tipo "Maybe" es bastante útil, pero tiene sus límites. Los principiantes tienden a entusiasmarse con "Maybe" y a usarlo en todas partes, aunque un tipo personalizado sería más apropiado.

Por ejemplo, supongamos que tenemos una aplicación de ejercicios en la que competimos contra nuestros amigos. Se empieza con una lista de nombres de amigos, pero se puede cargar más información de fitness sobre ellos más adelante. Podrías tener la tentación de modelarlo así:


type alias Friend =

  { name : String

  , age : Maybe Int

  , height : Maybe Float

  , weight : Maybe Float

  }


Toda la información está ahí, pero no estás modelando realmente el funcionamiento de tu aplicación. Sería mucho más preciso modelarlo así:

type Friend

  = Less String

  | More String Info


type alias Info =

  { age : Int

  , height : Float

  , weight : Float

  }


Este nuevo modelo captura mucha más información sobre tu aplicación. Solo hay dos situaciones reales. O solo tienes el nombre, o tienes el nombre y mucha información. En el código de tu vista, simplemente piensa si estás mostrando una vista Less o More del amigo. No tienes que responder preguntas como "¿qué pasa si tengo la edad pero no el peso?". ¡Eso no es posible con nuestro tipo más preciso!

La cuestión es que, si usas Maybe en todas partes, vale la pena examinar las definiciones de tipo y alias de tipo para ver si puedes encontrar una representación más precisa. ¡Esto suele dar lugar a muchas refactorizaciones útiles en tu código de actualización y vista!

El inventor de las referencias nulas, Tony Hoare, las describió así:

Lo llamo mi error de mil millones de dólares. Fue la invención de la referencia nula en 1965. En aquel entonces, estaba diseñando el primer sistema de tipos completo para referencias en un lenguaje orientado a objetos (ALGOL W). Mi objetivo era garantizar que todo uso de referencias fuera absolutamente seguro, con la comprobación realizada automáticamente por el compilador. Pero no pude resistir la tentación de incluir una referencia nula, simplemente por su facilidad de implementación. Esto ha provocado innumerables errores, vulnerabilidades y fallos del sistema, que probablemente han causado miles de millones de dólares en daños y perjuicios en los últimos cuarenta años.

Ese diseño hace que el fallo sea implícito. Cada vez que creas tener una cadena, podrías tener un valor nulo. ¿Deberías comprobarlo? ¿Lo comprobó quien te dio el valor? ¿Quizás no haya problema? ¿Quizás bloquee tu servidor? ¡Supongo que lo averiguaremos más adelante!

Elm evita estos problemas al no tener referencias nulas. En su lugar, usamos tipos personalizados como Maybe para que el fallo sea explícito. De esta forma, nunca hay sorpresas. Una cadena siempre es una cadena, y cuando ves una cadena Maybe, el compilador se asegurará de que se tengan en cuenta ambas variantes. De esta forma, obtienes la misma flexibilidad, pero sin los fallos inesperados.

viernes, 22 de agosto de 2025

Maybe en Elm


A medida que trabajemos más con Elm, veremos el tipo Maybe con bastante frecuencia. Se define así:


type Maybe a

  = Just a

  | Nothing


-- Just 3.14 : Maybe Float

-- Just "hi" : Maybe String

-- Just True : Maybe Bool

-- Nothing   : Maybe a


Este tipo tiene dos variantes: Nothing o Just a value. La variable de tipo permite tener Maybe Float y Maybe String según el valor.

Esto puede ser útil en dos escenarios principales: funciones parciales y campos opcionales.

A veces se necesita una función que responda a algunas entradas, pero no a otras. Mucha gente se encuentra con esto con String.toFloat al intentar convertir la entrada del usuario en números. Veámoslo en acción:


> String.toFloat

<function> : String -> Maybe Float


> String.toFloat "3.1415"

Just 3.1415 : Maybe Float


> String.toFloat "abc"

Nothing : Maybe Float


No todas las cadenas tienen sentido como números, así que esta función lo modela explícitamente. ¿Se puede convertir una cadena en un valor de punto flotante? ¡Quizás! A partir de ahí, podemos hacer una coincidencia de patrones con los datos resultantes y continuar según corresponda.

Otro lugar donde se ven comúnmente valores "Maybe" es en registros con campos opcionales.

Por ejemplo, supongamos que gestionamos una red social. Conectamos personas, buscamos amistad, etc. Ya conoces la explicación. The Onion describió nuestros verdaderos objetivos en 2011: extraer la mayor cantidad de datos posible para la CIA. Y si queremos todos los datos, debemos facilitar el acceso a los usuarios. Permitir que los añadan más adelante. Añadir funciones que los animen a compartir más y más información con el tiempo.

Comencemos con un modelo simple de usuario. Debe tener un nombre, pero la edad será opcional.


type alias User =

  { name : String

  , age : Maybe Int

  }


Ahora digamos que Sue crea una cuenta, pero decide no proporcionar su fecha de nacimiento:


sue : User

sue =

  { name = "Sue", age = Nothing }


Sin embargo, los amigos de Sue no pueden desearle un feliz cumpleaños. Me pregunto si realmente les importa... Más tarde, Tom crea un perfil y sí proporciona su edad:


tom : User

tom =

  { name = "Tom", age = Just 24 }


Genial, eso será genial en su cumpleaños. Pero lo más importante es que Tom forma parte de un grupo demográfico valioso. Los anunciantes estarán encantados.

Bien, ahora que tenemos algunos usuarios, ¿cómo podemos promocionarles alcohol sin infringir ninguna ley? Probablemente se enfadarían si lo hiciéramos para menores de 21 años, así que vamos a comprobarlo:


canBuyAlcohol : User -> Bool

canBuyAlcohol user =

  case user.age of

    Nothing ->

      False


    Just age ->

      age >= 21


Observa que el tipo Maybe nos obliga a realizar una coincidencia de patrones con la edad del usuario. De hecho, es imposible escribir código olvidando que los usuarios pueden no tener edad. ¡Elm se encarga de ello! Ahora podemos anunciar alcohol con la seguridad de que no estamos influyendo directamente en menores. Solo en sus compañeros mayores.


martes, 19 de agosto de 2025

Manejo de errores en Elm


Una de las garantías de Elm es que no se observarán errores de ejecución en la práctica. Esto se debe, en parte, a que Elm trata los errores como datos. En lugar de bloquearse, modelamos explícitamente la posibilidad de fallo con tipos personalizados. Por ejemplo, supongamos que desea convertir la entrada del usuario en una edad. Podría crear un tipo personalizado como este:


type MaybeAge

= Age Int

| InvalidInput


toAge : String -> MaybeAge

toAge userInput =

...


-- toAge "24" == Age 24

-- toAge "99" == Age 99

-- toAge "ZZ" == InvalidInput


Independientemente de la entrada que se proporcione a la función toAge, siempre produce un valor. Una entrada válida produce valores como Age 24 y Age 99, mientras que una entrada no válida produce el valor InvalidInput. A partir de ahí, utilizamos la coincidencia de patrones para garantizar que se consideren ambas posibilidades. ¡Sin bloqueos!

¡Este tipo de problemas surgen constantemente! Por ejemplo, quizás quieras convertir un conjunto de entradas de usuario en una publicación para compartir. Pero, ¿qué ocurre si olvidan añadir un título? ¿O si la publicación no tiene contenido? Podríamos modelar todos estos problemas explícitamente:


type MaybePost

= Post { title : String, content : String }

| NoTitle

| NoContent


toPost : String -> String -> MaybePost

toPost title content =

...


-- toPost "hi" "¿qué tal?" == Post { title = "hi", content = "¿qué tal?" }

-- toPost "" "" == NoTitle

-- toPost "hi" "" == NoContent


En lugar de simplemente indicar que la entrada no es válida, describimos cada una de las posibles causas del error. Si tenemos una función viewPreview : MaybePost -> Html msg para previsualizar publicaciones válidas, ahora podemos mostrar mensajes de error más específicos en el área de vista previa cuando algo falla.

Este tipo de situaciones son extremadamente comunes. Suele ser útil crear un tipo personalizado para cada situación, pero en algunos casos más sencillos, puede usar un tipo estándar. 



viernes, 15 de agosto de 2025

Pattern Matching en elm


Aprendimos a crear tipos personalizados con la palabra clave type. Nuestro ejemplo principal fue un usuario en una sala de chat:


type User

  = Regular String Int

  | Visitor String


Los usuarios habituales tienen nombre y edad, mientras que los visitantes solo tienen nombre. Ya tenemos nuestro tipo personalizado, pero ¿cómo lo usamos?

Supongamos que queremos una función toName que decida el nombre que se mostrará para cada usuario. Necesitamos usar una expresión case:


toName : User -> String

toName user =

  case user of

    Regular name age ->

      name

    Visitor name ->

      name


-- toName (Regular "Thomas" 44) == "Thomas"

-- toName (Visitor "kate95")    == "kate95"


La expresión case nos permite ramificar según la variante que veamos, así que, independientemente de si vemos a Thomas o a Kate, siempre sabremos cómo mostrar su nombre.

Y si probamos argumentos inválidos como toName (Visitar "kate95") o toName Anonymous, el compilador nos lo notifica inmediatamente. Esto significa que muchos errores simples se pueden corregir en segundos, en lugar de informar a los usuarios y consumir mucho más tiempo.

La función toName que acabamos de definir funciona de maravilla, pero ¿se da cuenta de que la edad no se utiliza en la implementación? Cuando algunos de los datos asociados no se utilizan, es habitual usar un comodín en lugar de asignarles un nombre:


toName : User -> String

toName user =

  case user of

    Regular name _ ->

      name

    Visitor name ->

      name


El _ reconoce los datos, pero también indica explícitamente que nadie los está utilizando.



sábado, 9 de agosto de 2025

Mensajes y Modelos en Elm


Ya vimos un par de ejemplos de definición de un tipo Msg. Este tipo de tipo es extremadamente común en Elm. En nuestra sala de chat, podríamos definir un tipo Msg de la siguiente manera:


type Msg

   = PressedEnter

   | ChangedDraft String

   | ReceivedMessage { user: User, message: String }

   | ClickedExit


Tenemos cuatro variantes. Algunas no tienen datos asociados, otras tienen muchos. Observe que ReceivedMessage tiene un registro como dato asociado. Esto es perfectamente correcto. ¡Cualquier tipo puede ser un dato asociado! Esto le permite describir las interacciones en su aplicación con mucha precisión.

Los tipos personalizados se vuelven extremadamente potentes cuando empieza a modelar situaciones con mucha precisión. Por ejemplo, si está esperando a que se carguen datos, podría querer modelarlos con un tipo personalizado como este:


type Profile

   = Failure

   | Loading

   | Success { name : String, description : String }


Así, puedes comenzar en el estado Loading y luego pasar a Failure o Success según lo que suceda. Esto simplifica enormemente la creación de una función de vista que siempre muestre algo razonable cuando se cargan los datos.

Los tipos personalizados son la característica más importante de Elm. Ofrecen mucha profundidad, especialmente una vez que te acostumbras a modelar escenarios con mayor precisión. 

viernes, 8 de agosto de 2025

Tipos personalizados en Elm



Hasta ahora hemos visto varios tipos como Bool, Int y String. Pero ¿cómo definimos los nuestros?

Supongamos que estamos creando una sala de chat. Todos necesitan un nombre, pero algunos usuarios no tienen una cuenta permanente. Simplemente dan un nombre cada vez que acceden.

Podemos describir esta situación definiendo el tipo UserStatus, enumerando todas las posibles variaciones:


type UserStatus = Regular | Visitor


El tipo UserStatus tiene dos variantes: un usuario puede ser Regular o un Visitante. Por lo tanto, podríamos representar a un usuario como un registro de la siguiente manera:


type UserStatus

   = Regular

   | Visitor


type alias User =

{ status : UserStatus

, name : String

}


thomas = { status = Regular, name = "Thomas" }

kate95 = { status = Visitor, name = "kate95" }


Ahora podemos rastrear si un usuario es Regular con cuenta o un Visitante de paso. No es muy complicado, ¡pero podemos simplificarlo!


En lugar de crear un tipo personalizado y un alias, podemos representar todo esto con un solo tipo personalizado. Las variantes Regular y Visitor tienen datos asociados. En nuestro caso, estos datos son un valor de cadena:


type User

   = Regular String

    | Visitor String


thomas = Regular "Thomas"

kate95 = Visitor "kate95"


Los datos se adjuntan directamente a la variante, por lo que ya no es necesario el registro.

Otra ventaja de este enfoque es que cada variante puede tener diferentes datos asociados. Supongamos que los usuarios Regulares proporcionaron su edad al registrarse. No hay una forma sencilla de capturar esto con registros, pero al definir tu propio tipo personalizado, no hay problema. Añadamos algunos datos asociados a la variante Regular :


> type User

   = Regular String Int

   | Visitor String


> Regular

<function> : String -> Int -> User


> Visitor

<function> : String -> User


> Regular "Thomas" 44

Regular "Thomas" 44 : User


> Visitor "kate95"

Visitor "kate95" : User


Solo hemos añadido la edad, pero las variantes de un tipo pueden variar bastante. Por ejemplo, quizá queramos añadir la ubicación de los usuarios Regulares para poder sugerir salas de chat regionales. O quizá queramos tener usuarios anónimos:


type User

  = Regular String Int Location

  | Visitor String

  | Anonymous



martes, 5 de agosto de 2025

Modelado en Elm


Los tipos personalizados se vuelven extremadamente potentes cuando se empiezan a modelar situaciones con mucha precisión. Por ejemplo, si se espera la carga de datos, se podría modelar con un tipo personalizado como este:


type Profile

   = Failure

   | Loading

   | Success { name : String, description : String }


De esta forma, se puede empezar en el estado de carga y luego pasar a Failure o Success según lo que ocurra. Esto simplifica enormemente la creación de una función de vista que siempre muestre algo razonable cuando se cargan los datos.

Los tipos personalizados son la característica más importante de Elm. Ofrecen mucha profundidad, especialmente una vez que se adquiere el hábito de modelar escenarios con mayor precisión. 

Mensajes en Elm


En este post vimos un par de ejemplos de definición de un tipo Msg. Este tipo de tipo es muy común en Elm. En nuestra sala de chat, podríamos definir un tipo Msg de la siguiente manera:


type Msg

    = PressedEnter

    | ChangedDraft String

    | ReceivedMessage {user: User, message: String}

    | ClickedExit


Tenemos cuatro variantes. Algunas no tienen datos asociados, otras sí. Observe que ReceivedMessage tiene un registro como dato asociado. Esto es perfectamente correcto. ¡Cualquier tipo puede ser un dato asociado! Esto le permite describir las interacciones en su aplicación con mucha precisión.


Constructores de Registros en Elm


Al crear un alias de tipo específico para un registro, también se genera un constructor de registros. Por lo tanto, si definimos un alias de tipo Usuario, podemos empezar a construir registros de la siguiente manera:


> type alias Usuario = { nombre: String, edad: Int}


> Usuario

<función>: String -> Int -> Usuario


> Usuario "Sue" 58

{ nombre = "Sue", edad = 58}: Usuario


> Usuario "Tom" 31

{ nombre = "Tom", edad = 31}: Usuario


Ten en cuenta que el orden de los argumentos en el constructor de registros coincide con el orden de los campos en el alias de tipo.


Reiteramos que esto es solo para registros. Crear alias de tipo para otros tipos no generará un constructor.

viernes, 1 de agosto de 2025

Modelos en Elm


Es muy común usar alias de tipo al diseñar un modelo. Cuando estábamos aprendiendo sobre la arquitectura de Elm, vimos un modelo como este:


type alias Model =

  { name : String

  , password : String

  , passwordAgain : String

  }


La principal ventaja de usar un alias de tipo para esto es que al escribir las anotaciones de tipo para las funciones de actualización y vista, es mucho más fácil escribir Msg -> Modelo -> Modelo que la versión completa. Además, permite añadir campos a nuestro modelo sin necesidad de modificar ninguna anotación de tipo.