Translate

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

miércoles, 4 de marzo de 2026

Elm y su Interoperabilidad con JavaScript

 


Ya hemos visto la arquitectura de Elm: tipos, comandos, suscripciones e incluso tenemos Elm instalado localmente.

Pero ¿qué sucede cuando necesitas integrarlo con JavaScript? Quizás exista una API de navegador que aún no tenga un paquete Elm equivalente. ¿Quizás quieras integrar un widget JavaScript en tu aplicación Elm? Veamos los tres mecanismos de interoperabilidad de Elm:

  • Banderas
  • Puertos
  • Elementos personalizados

Antes de analizar los tres mecanismos, necesitamos saber cómo compilar programas Elm a JavaScript.

Ejecutar elm make genera archivos HTML por defecto. Entonces, si dices:


elm make src/Main.elm


Se genera un archivo index.html que puedes abrir y empezar a experimentar. Si te estás iniciando en la interoperabilidad de JavaScript, te conviene generar archivos JavaScript:


elm make src/Main.elm --output=main.js


Esto genera un archivo JavaScript que expone una función Elm.Main.init(). Una vez que tengas main.js, puedes escribir tu propio archivo HTML con la función que desees.


Aquí está el HTML mínimo necesario para que tu archivo main.js aparezca en un navegador:


<html>

<head>

    <meta charset="UTF-8">

    <title>Main</title>

    <script src="main.js"></script>

</head>


<body>

    <div id="myapp"></div>

    <script>

            var app = Elm.Main.init({

                node: document.getElementById('myapp')

            });

    </script>

</body>

</html>


Quiero destacar las líneas importantes:

  • <head> - Tenemos una línea para cargar nuestro archivo main.js compilado. ¡Es obligatorio! Si compilas un módulo Elm llamado Main, obtendrás una función Elm.Main.init() disponible en JavaScript. Si compilas un módulo Elm llamado Home, obtendrás una función Elm.Home.init() en JavaScript. Etc.
  • <body> - Necesitamos hacer dos cosas aquí. Primero, creamos un <div> que queremos que nuestro programa Elm controle. ¿Quizás esté dentro de una aplicación más grande, rodeado de un montón de otras cosas? ¡No hay problema! Segundo, tenemos un <script> para inicializar nuestro programa Elm. Aquí llamamos a la función Elm.Main.init() para iniciar nuestro programa, pasando el nodo que queremos controlar.

Este es un archivo HTML normal, así que puedes incluir lo que quieras. Mucha gente carga archivos JS y CSS adicionales en el <head>. Esto significa que es perfectamente posible escribir el CSS a mano o generarlo de alguna manera. Agrega algo como <link rel="stylesheet" href="whatever-you-want.css"> en tu <head> y tendrás acceso a él. (También hay algunas opciones excelentes para especificar tu CSS dentro de Elm, ¡pero ese es otro tema!)

Ahora que sabemos cómo incrustar programas Elm en un documento HTML, es hora de empezar a explorar las tres opciones de interoperabilidad: indicadores, puertos y elementos personalizados en próximos posts.

martes, 3 de marzo de 2026

Empezando con Elm


Primero, instalemos elm, en ubuntu sería así: 


sudo apt update

sudo apt install nodejs npm -y

sudo npm install -g elm


Para ver si se instaló: 


elm --version


Ahora hagamos un nuevo proyecto, en una carpeta vacia pongamos: 


elm init


Y nos va a preguntar si queremos crear un archivo elm.json y un directorio src/:


elm.json describe tu proyecto.

src/ contiene todos tus archivos Elm.


Decile que si y metele. 


Ahora vamos a crear un archivo llamado src/Main.elm en tu editor y copia el código del ejemplo de los botones.


elm reactor te ayuda a crear proyectos Elm sin tener que modificar demasiado la terminal. Simplemente ejecútalo en la raíz de tu proyecto, así:


elm reactor


Esto inicia un servidor en http://localhost:8000. Puedes navegar a cualquier archivo Elm y ver su aspecto. Ejecuta elm reactor, sigue el enlace localhost e intenta revisar tu archivo src/Main.elm.


Puedes compilar código Elm a HTML o JavaScript con comandos como este:


elm make


# Crea un archivo index.html que puedas abrir en tu navegador.

# Crea un archivo index.html que puedas abrir en tu navegador. elm make src/Main.elm


# Crea un archivo JS optimizado para incrustarlo en un documento HTML personalizado.


elm make src/Main.elm --optimize --output=elm.js


Esta es la forma más general de compilar código Elm. Es extremadamente útil cuando tu proyecto se vuelve demasiado avanzado para Elm Reactor.


elm install


Todos los paquetes de Elm se encuentran en package.elm-lang.org.

Supongamos que buscas y decides que necesitas elm/http y elm/json para realizar algunas solicitudes HTTP. Puedes configurarlos en tu proyecto con los siguientes comandos:


elm install elm/http

elm install elm/json


Esto añade estas dependencias a tu archivo elm.json, haciendo que estos paquetes estén disponibles en tu proyecto. Esto te permitirá importar Http y usar funciones como Http.get en tus programas.


Consejos y trucos:

  • Primero, ¡no te preocupes por recordar todo esto!
  • Siempre puedes ejecutar elm --help para obtener una descripción completa de las capacidades de Elm.
  • También puedes ejecutar comandos como elm make --help y elm repl --help para obtener sugerencias sobre un comando específico. Esto es genial si quieres comprobar qué indicadores están disponibles y qué hacen.
  • Segundo, no te preocupes si te lleva un tiempo familiarizarte con la terminal en general.
  • Llevo usándola más de una década y todavía no recuerdo cómo comprimir archivos, encontrar todos los archivos de Elm en un directorio, etc. ¡Todavía busco información!


Ahora que tenemos nuestro editor configurado y elm disponible en la terminal, ¡volvamos a aprender Elm!

domingo, 1 de marzo de 2026

Reloj digital en elm


Hasta ahora nos hemos centrado en los comandos. Con los ejemplos de HTTP y aleatoriedad, le ordenamos a Elm que realizara una tarea específica inmediatamente, pero ese es un patrón un tanto extraño para un reloj. Siempre queremos saber la hora actual. ¡Aquí es donde entran en juego las suscripciones!


import Browser

import Html exposing (..)

import Task

import Time




-- MAIN



main =

  Browser.element

    { init = init

    , view = view

    , update = update

    , subscriptions = subscriptions

    }




-- MODEL



type alias Model =

  { zone : Time.Zone

  , time : Time.Posix

  }



init : () -> (Model, Cmd Msg)

init _ =

  ( Model Time.utc (Time.millisToPosix 0)

  , Task.perform AdjustTimeZone Time.here

  )




-- UPDATE



type Msg

  = Tick Time.Posix

  | AdjustTimeZone Time.Zone




update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    Tick newTime ->

      ( { model | time = newTime }

      , Cmd.none

      )


    AdjustTimeZone newZone ->

      ( { model | zone = newZone }

      , Cmd.none

      )




-- SUBSCRIPTIONS



subscriptions : Model -> Sub Msg

subscriptions model =

  Time.every 1000 Tick




-- VIEW



view : Model -> Html Msg

view model =

  let

    hour   = String.fromInt (Time.toHour   model.zone model.time)

    minute = String.fromInt (Time.toMinute model.zone model.time)

    second = String.fromInt (Time.toSecond model.zone model.time)

  in

  h1 [] [ text (hour ++ ":" ++ minute ++ ":" ++ second) ]


Todo lo nuevo proviene del paquete elm/time. 


Para trabajar con el tiempo correctamente en programación, necesitamos tres conceptos diferentes:

Hora Humana: Es lo que se ve en los relojes (8 a. m.) o en los calendarios (3 de mayo). ¡Genial! Pero si mi llamada es a las 8 a. m. en Boston, ¿qué hora es para mi amigo en Vancouver? Si es a las 8 a. m. en Tokio, ¿es el mismo día en Nueva York? (¡No!) Por lo tanto, entre las zonas horarias basadas en fronteras políticas en constante cambio y el uso inconsistente del horario de verano, ¡la hora humana básicamente nunca debería almacenarse en el modelo ni en la base de datos! ¡Es solo para visualización!

POSIX Time: Con POSIX Time, no importa dónde vivas ni en qué época del año sea. Es simplemente la cantidad de segundos transcurridos desde un momento arbitrario (en 1970). Donde quiera que vayas en la Tierra, la hora POSIX es la misma.

Zonas Horarias: Una "zona horaria" es un conjunto de datos que te permite convertir la hora POSIX en hora humana. ¡Pero no se trata solo de UTC-7 o UTC+3! Las zonas horarias son mucho más complicadas que una simple diferencia horaria. Cada vez que Florida cambia al horario de verano para siempre o Samoa cambia de UTC-11 a UTC+13, alguien añade una nota a la base de datos de zonas horarias de la IANA. Esa base de datos se carga en cada ordenador, y entre la hora POSIX y todos los casos excepcionales de la base de datos, ¡podemos calcular la hora humana!


Así que, para mostrarle la hora a un ser humano, siempre debes conocer Time.Posix y Time.Zone. ¡Eso es todo! Así que todo lo relacionado con la "hora humana" es para la función de vista, no para el modelo. De hecho, puedes verlo en nuestra vista:


vista: Modelo -> Mensaje HTML

vista modelo =

let

hora = String.fromInt (Time.toHour modelo.zone modelo.time)

minuto = String.fromInt (Time.toMinute modelo.zone modelo.time)

segundo = String.fromInt (Time.toSecond modelo.zone modelo.time)

in

h1 [] [texto (hora ++ ":" ++ minuto ++ ":" ++ segundo) ]


La función Time.toHour toma Time.Zone y Time.Posix nos devuelve un entero de 0 a 23 que indica qué hora es en tu zona horaria.


¿Cómo obtenemos nuestro Time.Posix? ¡Con una suscripción! Suscripciones: Modelo -> Submensaje


subscriptions : Model -> Sub Msg

subscriptions model =

  Time.every 1000 Tick


Usamos la función Time.every:


every : Float -> (Time.Posix -> msg) -> Sub msg


Requiere dos argumentos:

  • Un intervalo de tiempo en milisegundos. Dijimos 1000, que significa cada segundo. Pero también podríamos decir 60 * 1000 por cada minuto, o 5 * 60 * 1000 por cada cinco minutos.
  • Una función que convierte la hora actual en un mensaje. Por lo tanto, cada segundo, la hora actual se convertirá en un Tick <time> para nuestra función de actualización.

Ese es el patrón básico de cualquier suscripción. Se proporciona cierta configuración y se describe cómo generar valores de mensaje. 

Obtener Time.Zone es un poco más complicado. Nuestro programa creó un comando con:


Task.perform AdjustTimeZone Time.here


Leer la documentación de la tarea es la mejor manera de comprender esta línea. La documentación está escrita para explicar los nuevos conceptos, y creo que sería una digresión excesiva incluir una versión peor de esa información aquí. La cuestión es simplemente que ordenamos al entorno de ejecución que nos proporcione la Time.Zone dondequiera que se ejecute el código.

miércoles, 18 de febrero de 2026

Controlando los Efectos Colaterales: Elm, Akka y Koka


Los efectos colaterales son inevitables en cualquier programa real. Tarde o temprano necesitamos leer datos, escribir en pantalla, hacer una petición HTTP o guardar algo en una base de datos.

El problema no está en tener efectos, sino en no saber dónde y cuándo ocurren.


Lenguajes y frameworks como Elm, Akka y Koka proponen tres enfoques distintos para enfrentar este desafío, pero todos comparten la misma filosofía:

Mantener la lógica pura y controlada, y ejecutar los efectos en un punto bien definido.


Elm es un lenguaje funcional puro que se ejecuta en el navegador. En Elm, ninguna función puede tener efectos secundarios directamente.

En su lugar, el programa describe qué debería pasar, y el runtime de Elm se encarga de hacerlo realidad.


Por ejemplo, el corazón de un programa Elm suele tener esta función:


update : Msg -> Model -> (Model, Cmd Msg)


Cuando el usuario genera un mensaje (Msg), la función update:

  • recibe el estado actual (Model),
  • devuelve un nuevo modelo,
  • y opcionalmente un comando (Cmd Msg), que describe un efecto.


El punto clave es que update no ejecuta el efecto; simplemente lo describe.

Es el runtime de Elm quien decide cuándo y cómo hacerlo.

Así, todo el código de tu aplicación es puro y determinista, y los efectos están completamente bajo control.


Un ejemplo simple:


update msg model =

    case msg of

        LoadData ->

            ( model, Http.get { url = "/data", expect = GotData } )


        GotData response ->

            ( { model | data = response }, Cmd.none )


Aquí, LoadData devuelve una descripción de un efecto HTTP, pero Elm no lo ejecuta directamente.

La ejecución real ocurre más tarde, en un punto centralizado del runtime.


Akka, en el mundo de Scala y Java, aplica un principio muy similar desde la programación concurrente.

En Akka, los actores son unidades independientes que:

  • procesan un mensaje a la vez,
  • mantienen su propio estado interno,
  • y pueden enviar mensajes a otros actores.


Cada actor es como una pequeña cápsula que contiene su estado y sus efectos.

Nada del mundo exterior puede acceder a su estado directamente; solo se comunica mediante mensajes.


Por ejemplo:


class Counter extends Actor {

  var count = 0


  def receive: Receive = {

    case "inc" =>

      count += 1

      sender() ! count

  }

}


Este actor mantiene su propio contador y solo responde a mensajes.

El efecto (enviar una respuesta, escribir logs, actualizar estado) está encapsulado dentro del actor.


El runtime de Akka —el ActorSystem— se encarga de distribuir los mensajes y ejecutar los efectos concurrentemente, garantizando aislamiento y seguridad.

Desde afuera, un actor parece una función pura: recibe un mensaje y devuelve una respuesta.

Pero los efectos reales ocurren dentro del sistema, no en tu código.


Koka lleva la idea aún más lejos.

En lugar de confiar en un runtime que ejecuta los efectos, Koka los modela en el sistema de tipos.

Cada función en Koka declara explícitamente qué efectos puede producir.

Por ejemplo:


fun add(x : int, y : int) : int {

  x + y

}


Esta función es completamente pura.

En cambio:


fun printAdd(x : int, y : int) : <io> int {

  println("Sumando...")

  x + y

}


Aquí, el tipo <io> indica que esta función puede realizar operaciones de entrada/salida.

Koka rastrea los efectos de manera estática: el compilador sabe exactamente qué partes del programa son puras y cuáles no.


Esto permite escribir programas donde los efectos son explícitos, controlados y predecibles.

No hace falta leer todo el código para saber si una función puede modificar el mundo: el tipo te lo dice.


Aunque Elm, Akka y Koka se desarrollan en contextos muy diferentes —frontend funcional, backend concurrente y teoría de tipos—, los tres comparten una visión profunda de la pureza y el control de efectos.

  • Elm describe los efectos y delega su ejecución al runtime.
  • Akka encapsula los efectos en actores que procesan mensajes de forma aislada.
  • Koka anota los efectos en los tipos, haciéndolos explícitos desde el nivel más básico del lenguaje.


En los tres casos, la meta es la misma: escribir código predecible, testeable y sin sorpresas.


miércoles, 11 de febrero de 2026

Generar valores aleatorios en Elm



Hasta ahora solo hemos visto comandos para realizar solicitudes HTTP, pero también podemos ordenar otras cosas, como generar valores aleatorios. Vamos a crear una aplicación que tira dados y produce un número aleatorio entre 1 y 6.


import Browser

import Html exposing (..)

import Html.Events exposing (..)

import Random


-- MAIN

main =

  Browser.element

    { init = init

    , update = update

    , subscriptions = subscriptions

    , view = view

    }


-- MODEL

type alias Model =

  { dieFace : Int

  }


init : () -> (Model, Cmd Msg)

init _ =

  ( Model 1

  , Cmd.none

  )


-- UPDATE

type Msg

  = Roll

  | NewFace Int


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    Roll ->

      ( model

      , Random.generate NewFace (Random.int 1 6)

      )


    NewFace newFace ->

      ( Model newFace

      , Cmd.none

      )


-- SUBSCRIPTIONS

subscriptions : Model -> Sub Msg

subscriptions model =

  Sub.none


-- VIEW

view : Model -> Html Msg

view model =

  div []

    [ h1 [] [ text (String.fromInt model.dieFace) ]

    , button [ onClick Roll ] [ text "Roll" ]

    ]


La novedad es el comando que se ejecuta en la función de actualización:


Random.generate NewFace (Random.int 1 6)


La generación de valores aleatorios funciona de forma ligeramente diferente a la de lenguajes como JavaScript, Python, Java, etc. ¡Veamos cómo funciona en Elm!


Para ello, utilizamos el paquete elm/random, en particular el módulo Random.

La idea principal es que contamos con un generador aleatorio que describe cómo generar un valor aleatorio. Por ejemplo:


import Random


probability : Random.Generator Float

probability =

  Random.float 0 1


roll : Random.Generator Int

roll =

  Random.int 1 6


usuallyTrue : Random.Generator Bool

usuallyTrue =

  Random.weighted (80, True) [ (20, False) ]


Aquí tenemos tres generadores aleatorios. El generador "roll" indica que producirá un entero, y más específicamente, un entero entre 1 y 6 inclusive. Del mismo modo, el generador "usuallyTrue" indica que producirá un booleano, y más específicamente, será verdadero el 80% de las veces.

La cuestión es que todavía no estamos generando los valores. Simplemente estamos describiendo cómo generarlos. A partir de ahí, se usa "Random.generate" para convertirlo en un comando:

generate : (a -> msg) -> Generator a -> Cmd msg

Al ejecutar el comando, el generador genera un valor que se convierte en un mensaje para la función de actualización. En nuestro ejemplo, el generador genera un valor entre 1 y 6, que se convierte en un mensaje como NewFace 1 o NewFace 4. Eso es todo lo que necesitamos para obtener nuestras tiradas de dados aleatorias, ¡pero los generadores pueden hacer mucho más!

Una vez que tengamos generadores simples como probability y usuallyTrue, podemos empezar a combinarlos con funciones como map3. Imaginemos que queremos crear una máquina tragamonedas sencilla. Podríamos crear un generador como este:


import Random


type Symbol = Cherry | Seven | Bar | Grapes


symbol : Random.Generator Symbol

symbol =

  Random.uniform Cherry [ Seven, Bar, Grapes ]


type alias Spin =

  { one : Symbol

  , two : Symbol

  , three : Symbol

  }


spin : Random.Generator Spin

spin =

  Random.map3 Spin symbol symbol symbol


Primero creamos un símbolo para describir las imágenes que pueden aparecer en la máquina tragamonedas. Luego, creamos un generador aleatorio que genera cada símbolo con la misma probabilidad.

A partir de ahí, usamos map3 para combinarlos en un nuevo generador de giros. Este indica que se deben generar tres símbolos y luego combinarlos para formar un giro.

La idea es que, a partir de pequeños bloques, podemos crear un generador que describe un comportamiento bastante complejo. Y luego, desde nuestra aplicación, solo tenemos que ejecutar algo como Random.generate NewSpin spin para obtener el siguiente valor aleatorio.

domingo, 1 de febrero de 2026

JSON Decoders de elm



El paquete elm/json nos proporciona el módulo Json.Decode. Está lleno de pequeños decodificadores que podemos conectar.

Para obtener "edad" de { "nombre": "Tom", "edad": 42 }, crearíamos un decodificador como este:


import Json.Decode exposing (Decoder, field, int)


ageDecoder : Decoder Int

ageDecoder =

  field "age" int


 -- int : Decoder Int

 -- field : String -> Decoder a -> Decoder a


La función campo toma dos argumentos:

String: el nombre del campo. Por lo tanto, solicitamos un objeto con un campo "edad".

Decoder a: el decodificador que se probará a continuación. Si el campo "edad" existe, probaremos este decodificador con el valor que contiene.

En resumen, el campo "edad" int solicita un campo "edad" y, si existe, ejecuta el Decoder Int para intentar extraer un entero.

Hacemos prácticamente lo mismo para extraer el campo "nombre":


import Json.Decode exposing (Decoder, field, string)


nameDecoder : Decoder String

nameDecoder =

  field "name" string


-- string : Decoder String


En este caso, solicitamos un objeto con un campo "nombre" y, si existe, queremos que su valor sea una cadena.


¿Y si queremos decodificar dos campos? Unimos los decodificadores con map2:


map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value

Esta función toma dos decodificadores. Los prueba y combina sus resultados. Ahora podemos combinar dos decodificadores diferentes:


import Json.Decode exposing (Decoder, map2, field, string, int)


type alias Person =

  { name : String

  , age : Int

  }


personDecoder : Decoder Person

personDecoder =

  map2 Person

      (field "name" string)

      (field "age" int)


Por lo tanto, si usáramos personDecoder en { "name": "Tom", "age": 42 }, obtendríamos un valor Elm como Person "Tom" 42.

Si realmente quisiéramos adentrarnos en la esencia de los decodificadores, definiríamos personDecoder como map2 Person nameDecoder ageDecoder utilizando nuestras definiciones anteriores. ¡Siempre conviene construir los decodificadores a partir de bloques más pequeños!

Muchos datos JSON no son tan claros ni planos. Imagina si /api/random-quotes/v2 se hubiera publicado con información más completa sobre los autores:


{

  "quote": "December used to be a month but it is now a year",

  "source": "Letters from a Stoic",

  "author":

  {

    "name": "Seneca",

    "age": 68,

    "origin": "Cordoba"

  },

  "year": 54

}

Podríamos gestionar este nuevo escenario anidando nuestros decodificadores:

import Json.Decode exposing (Decoder, map2, map4, field, int, string)


type alias Quote =

  { quote : String

  , source : String

  , author : Person

  , year : Int

  }


quoteDecoder : Decoder Quote

quoteDecoder =

  map4 Quote

    (field "quote" string)

    (field "source" string)

    (field "author" personDecoder)

    (field "year" int)


type alias Person =

  { name : String

  , age : Int

  }


personDecoder : Decoder Person

personDecoder =

  map2 Person

    (field "name" string)

    (field "age" int)


Observe que no nos molestamos en decodificar el campo "origen" del autor. Los decodificadores no tienen ningún problema en omitir campos, lo que puede ser útil al extraer poca información de valores JSON muy grandes.

Json en Elm




Acabamos de ver un ejemplo que usa HTTP para obtener el contenido de un libro. Pero muchos servidores devuelven datos en un formato especial llamado Notación de Objetos JavaScript, o JSON.

Nuestro siguiente ejemplo muestra cómo obtener datos JSON, lo que nos permite pulsar un botón para mostrar citas aleatorias de una selección aleatoria de libros. 


import Browser

import Html exposing (..)

import Html.Attributes exposing (style)

import Html.Events exposing (..)

import Http

import Json.Decode exposing (Decoder, map4, field, int, string)


-- MAIN

main =

  Browser.element

    { init = init

    , update = update

    , subscriptions = subscriptions

    , view = view

    }


-- MODEL

type Model

  = Failure

  | Loading

  | Success Quote


type alias Quote =

  { quote : String

  , source : String

  , author : String

  , year : Int

  }


init : () -> (Model, Cmd Msg)

init _ =

  (Loading, getRandomQuote)


-- UPDATE

type Msg

  = MorePlease

  | GotQuote (Result Http.Error Quote)


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    MorePlease ->

      (Loading, getRandomQuote)


    GotQuote result ->

      case result of

        Ok quote ->

          (Success quote, Cmd.none)


        Err _ ->

          (Failure, Cmd.none)


-- SUBSCRIPTIONS

subscriptions : Model -> Sub Msg

subscriptions model =

  Sub.none


-- VIEW

view : Model -> Html Msg

view model =

  div []

    [ h2 [] [ text "Random Quotes" ]

    , viewQuote model

    ]


viewQuote : Model -> Html Msg

viewQuote model =

  case model of

    Failure ->

      div []

        [ text "I could not load a random quote for some reason. "

        , button [ onClick MorePlease ] [ text "Try Again!" ]

        ]


    Loading ->

      text "Loading..."


    Success quote ->

      div []

        [ button [ onClick MorePlease, style "display" "block" ] [ text "More Please!" ]

        , blockquote [] [ text quote.quote ]

        , p [ style "text-align" "right" ]

            [ text "— "

            , cite [] [ text quote.source ]

            , text (" by " ++ quote.author ++ " (" ++ String.fromInt quote.year ++ ")")

            ]

        ]


-- HTTP

getRandomQuote : Cmd Msg

getRandomQuote =

  Http.get

    { url = "https://elm-lang.org/api/random-quotes"

    , expect = Http.expectJson GotQuote quoteDecoder

    }


quoteDecoder : Decoder Quote

quoteDecoder =

  map4 Quote

    (field "quote" string)

    (field "source" string)

    (field "author" string)

    (field "year" int)


Este ejemplo es bastante similar al anterior:

  • init inicia en el estado Cargando, con un comando para obtener una cita aleatoria.
  • update gestiona el mensaje GotQuote cuando hay una nueva cita disponible. Pase lo que pase, no tenemos comandos adicionales. También gestiona el mensaje MorePlease cuando alguien presiona el botón, emitiendo un comando para obtener más citas aleatorias.
  • view muestra las citas!

La principal diferencia está en la definición de getRandomCatGif. En lugar de usar Http.expectString, hemos cambiado a Http.expectJson. ¿Qué ocurre con esto?


Cuando se solicita a /api/random-quotes una cita aleatoria, el servidor produce una cadena JSON como esta:


{

"quote": "Diciembre solía ser un mes, pero ahora es un año",

"source": "Cartas de un estoico",

"author": "Séneca",

"year": 54

}


No ofrecemos garantías sobre la información aquí contenida. El servidor puede cambiar los nombres de los campos, y estos pueden tener diferentes tipos en distintas situaciones. ¡Es un mundo complejo!

En JavaScript, el enfoque consiste en convertir JSON en objetos JavaScript y esperar que todo salga bien. Pero si hay algún error tipográfico o datos inesperados, se produce una excepción de tiempo de ejecución en alguna parte del código. ¿Era incorrecto el código? ¿Eran incorrectos los datos? ¡Es hora de investigar para averiguarlo!

En Elm, validamos el JSON antes de que llegue a nuestro programa. Por lo tanto, si los datos tienen una estructura inesperada, lo detectamos de inmediato. Es imposible que datos incorrectos se filtren y provoquen una excepción de tiempo de ejecución tres archivos después. Esto se logra con decodificadores JSON.

viernes, 30 de enero de 2026

HTTP en elm

 


A menudo resulta útil obtener información de otras fuentes de internet.

Por ejemplo, supongamos que queremos cargar el texto completo de "Opinión Pública" de Walter Lippmann. Publicado en 1922, este libro ofrece una perspectiva histórica sobre el auge de los medios de comunicación y sus implicaciones para la democracia. Para nuestros propósitos, nos centraremos en cómo usar el paquete elm/http para integrar este libro en nuestro programa.

Si queres ver el ejemplo en acción, hace click en este link: https://elm-lang.org/examples/book


import Browser

import Html exposing (Html, text, pre)

import Http


-- MAIN


main =

  Browser.element

    { init = init

    , update = update

    , subscriptions = subscriptions

    , view = view

    }


-- MODEL


type Model

  = Failure

  | Loading

  | Success String


init : () -> (Model, Cmd Msg)

init _ =

  ( Loading

  , Http.get

      { url = "https://elm-lang.org/assets/public-opinion.txt"

      , expect = Http.expectString GotText

      }

  )


-- UPDATE


type Msg

  = GotText (Result Http.Error String)


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    GotText result ->

      case result of

        Ok fullText ->

          (Success fullText, Cmd.none)


        Err _ ->

          (Failure, Cmd.none)


-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg

subscriptions model =

  Sub.none


-- VIEW


view : Model -> Html Msg

view model =

  case model of

    Failure ->

      text "I was unable to load your book."


    Loading ->

      text "Loading..."


    Success fullText ->

      pre [] [ text fullText ]


Algunas partes de esto deberían resultar familiares de ejemplos anteriores de la arquitectura Elm. Seguimos teniendo un modelo de nuestra aplicación. Seguimos teniendo una actualización que reacciona a los mensajes. Seguimos teniendo una función de vista que muestra todo en pantalla.

Las nuevas partes amplían el patrón principal que vimos antes con algunos cambios en init y update, y la incorporación de la suscripción.

La función init describe cómo inicializar nuestro programa:


init : () -> (Model, Cmd Msg)

init _ =

  ( Loading

  , Http.get

      { url = "https://elm-lang.org/assets/public-opinion.txt"

      , expect = Http.expectString GotText

      }

  )

Como siempre, debemos generar el modelo inicial, pero ahora también generamos un comando que indica lo que queremos hacer inmediatamente. Ese comando generará un mensaje que se introduce en la función de actualización.

Nuestro sitio web del libro se inicia en estado de carga y queremos obtener el texto completo. Al realizar una solicitud GET con Http.get, especificamos la URL de los datos que queremos obtener y qué datos esperamos que contengan. En nuestro caso, la URL apunta a datos del sitio web de Elm y esperamos que sea una cadena grande que podamos mostrar en pantalla.

La línea Http.expectString GotText indica algo más que esperamos una cadena. También indica que, cuando recibamos una respuesta, esta debe convertirse en un mensaje GotText:


type Msg

  = GotText (Result Http.Error String)


-- GotText (Ok "The Project Gutenberg EBook of ...")

-- GotText (Err Http.NetworkError)

-- GotText (Err (Http.BadStatus 404))


Tengamos en cuenta que usamos el tipo Result, mencionado en un par de secciones anteriores. Esto nos permite considerar completamente los posibles fallos en nuestra función de actualización. Hablando de funciones de actualización...

Nuestra función de actualización también devuelve un poco más de información:


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    GotText result ->

      case result of

        Ok fullText ->

          (Success fullText, Cmd.none)


        Err _ ->

          (Failure, Cmd.none)


Al observar la firma de tipo, vemos que no solo devolvemos un modelo actualizado. También generamos un comando que indica lo que queremos que haga Elm.

Pasando a la implementación, realizamos la coincidencia de patrones con los mensajes de forma normal. Cuando llega un mensaje GotText, inspeccionamos el resultado de nuestra solicitud HTTP y actualizamos nuestro modelo según si fue un éxito o un fracaso. La novedad es que también proporcionamos un comando.

En caso de obtener el texto completo correctamente, ejecutamos Cmd.none para indicar que no hay más trabajo que hacer. ¡Ya obtuvimos el texto completo!

Y en caso de algún error, también ejecutamos Cmd.none y simplemente nos rendimos. El texto del libro no se cargó. Si quisiéramos ser más sofisticados, podríamos realizar la coincidencia de patrones con Http.Error y reintentar la solicitud si se agota el tiempo de espera o algo similar.

La cuestión es que, independientemente de cómo decidamos actualizar nuestro modelo, también podemos ejecutar nuevos comandos. ¡Necesito más datos! ¡Quiero un número aleatorio! Etc.

La otra novedad de este programa es la función de suscripción. Permite consultar el modelo y decidir si se desea suscribirse a cierta información. En nuestro ejemplo, usamos Sub.none para indicar que no necesitamos suscribirnos a nada, pero pronto veremos un ejemplo de un reloj al que queremos suscribirnos a la hora actual.

Al crear un programa con Browser.element, configuramos un sistema como este:

Podemos ejecutar comandos desde init y update. Esto nos permite, por ejemplo, realizar solicitudes HTTP cuando queramos. También podemos suscribirnos a información interesante. (¡Veremos un ejemplo de suscripciones más adelante!)


miércoles, 17 de diciembre de 2025

Consumir una API REST desde Elm


La idea es que simplemente pintemos "hola mundo" con él consumiendo una API Rest: 


module Main exposing (..)


import Browser

import Html exposing (Html, div, text, button)

import Html.Events exposing (onClick)

import Http

import Json.Decode exposing (Decoder, field, string)



-- MODEL


type alias Model =

    { message : String

    , loading : Bool

    }


init : () -> ( Model, Cmd Msg )

init _ =

    ( { message = "", loading = False }, Cmd.none )


-- UPDATE


type Msg

    = FetchMessage

    | GotMessage (Result Http.Error String)



update : Msg -> Model -> ( Model, Cmd Msg )

update msg model =

    case msg of

        FetchMessage ->

            ( { model | loading = True }

            , getMessage

            )


        GotMessage (Ok message) ->

            ( { model | message = message, loading = False }, Cmd.none )


        GotMessage (Err _) ->

            ( { model | message = "Error fetching message", loading = False }, Cmd.none )




-- HTTP REQUEST


getMessage : Cmd Msg

getMessage =

    Http.get

        { url = "http://localhost:8080/hello"

        , expect = Http.expectString GotMessage

        }


-- VIEW


view : Model -> Html Msg

view model =

    div []

        [ button [ onClick FetchMessage ] [ text "Fetch Message" ]

        , div []

            [ text

                (if model.loading then

                    "Loading..."

                 else

                    model.message

                )

            ]

        ]




-- MAIN


main : Program () Model Msg

main =

    Browser.element

        { init = init

        , update = update

        , subscriptions = always Sub.none

        , view = view

        }


  • El modelo (Model) guarda el mensaje recibido y un flag de carga (loading).
  • Al hacer clic en el botón, se dispara el mensaje FetchMessage, que a su vez ejecuta getMessage.
  • Http.get realiza una petición a la URL http://localhost:8080/hello.
  • Si la respuesta es exitosa, el mensaje GotMessage (Ok ...) actualiza el modelo con el texto devuelto por el servidor.
  • La vista muestra un botón y el resultado de la petición (o un “Loading...” mientras espera).


Podés usar cualquier backend —por ejemplo, un mini servidor Node.js o Express:


// server.js

import express from "express";

const app = express();


app.get("/hello", (req, res) => {

  res.send("Hello from the API!");

});


app.listen(8080, () => console.log("API running on http://localhost:8080/hello"));


miércoles, 12 de noviembre de 2025

Elm vs Haskell: dos caminos del paradigma funcional


Haskell y Elm comparten una raíz común: ambos son lenguajes funcionales puros, con tipado estático, inferencia de tipos y un fuerte énfasis en la inmutabilidad.

Sin embargo, cada uno tomó un camino distinto.

Haskell apostó por la abstracción, la teoría de tipos avanzada y la expresividad; Elm, por la simplicidad, la seguridad y la experiencia del desarrollador.

Haskell nació en el ámbito académico, con el objetivo de ser un lenguaje funcional puro que sirviera como base de investigación.

Por eso prioriza la expresividad, la abstracción y la corrección formal.

Su lema podría ser: “todo puede expresarse en tipos”.

Elm, en cambio, nació del mundo web.

Su meta no es la investigación, sino la confiabilidad.

Fue diseñado para construir interfaces web sin errores en tiempo de ejecución.

Su lema podría ser: “ningún runtime exception, nunca”.


En resumen:

Haskell es una herramienta para explorar los límites del paradigma funcional.

Elm es una herramienta para aplicar ese paradigma con seguridad y pragmatismo.


Ambos tienen tipado estático e inferencia, pero el sistema de tipos de Haskell es mucho más poderoso.

Haskell permite type classes, kind polymorphism, type families, GADTs, monads, existentials y un sinfín de extensiones.

Elm tiene un sistema de tipos mucho más pequeño, pero más legible y predecible.

En Haskell, el tipo puede expresar conceptos avanzados:


fmap :: Functor f => (a -> b) -> f a -> f b


Mientras que en Elm, los tipos se mantienen simples y directos:


List.map : (a -> b) -> List a -> List b


En Haskell podés crear tus propias type classes (Eq, Ord, Monad, etc.).

En Elm no existen type classes: sólo hay un conjunto fijo de restricciones (number, comparable, appendable).


Haskell es un lenguaje donde los tipos son una herramienta de abstracción.

Elm los usa más bien como una herramienta de seguridad.


Tanto Haskell como Elm son lenguajes funcionales puros: ninguna función puede tener efectos secundarios sin declararlo explícitamente.

Pero los abordan de forma distinta.


Haskell utiliza el sistema de monads para modelar efectos: IO, Maybe, State, etc.

Cada efecto se encapsula en un tipo, y se encadenan usando do notation.


main :: IO ()

main = do

    name <- getLine

    putStrLn ("Hola, " ++ name)


Elm evita completamente las monads.

En su lugar, usa un modelo explícito de efectos: los Commands (Cmd) y Subscriptions (Sub), que forman parte del Elm Architecture.

Esto mantiene la pureza del lenguaje sin exponer al programador a conceptos teóricos complejos.


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        CargarDatos ->

            ( model, Http.get {...} )


        DatosCargados datos ->

            ( { model | items = datos }, Cmd.none )


Haskell ofrece poder; Elm ofrece control.

En Haskell, el manejo de efectos es flexible y extensible.

En Elm, es seguro y predecible.


Elm está diseñado para ser simple y consistente.

La sintaxis es limpia, sin operadores ambiguos ni extensiones opcionales.

Haskell, en cambio, permite una gran expresividad, pero también puede resultar críptico.


En Elm:


sumar : Int -> Int -> Int

sumar x y =

    x + y


En Haskell:


sumar :: Int -> Int -> Int

sumar x y = x + y


Parecen casi iguales, pero Haskell permite redefinir operadores, crear infix personalizados o usar point-free style, lo que puede aumentar la complejidad.

Elm evita deliberadamente esa flexibilidad para mantener el código legible para todos.


Haskell es un lenguaje generalista: se usa en compiladores, sistemas financieros, backends web, análisis estático y más.

Su ecosistema es vasto y diverso, aunque muchas librerías varían en calidad y mantenimiento.


Elm se centra exclusivamente en el frontend web.

Todo su diseño gira en torno a construir aplicaciones en el navegador.

No hay ambigüedad: un proyecto Elm siempre es una aplicación web.

A cambio de esa limitación, ofrece una experiencia coherente, con un compilador extremadamente útil y mensajes de error ejemplares.


La diferencia más grande entre ambos lenguajes quizá sea emocional. Haskell a veces puede parecer un rompecabezas: poderoso, elegante, pero con una curva de aprendizaje pronunciada.

Elm, en cambio, busca que programar sea agradable, incluso para quienes no tienen experiencia previa en programación funcional.

El compilador de Elm no sólo te dice qué está mal, sino cómo arreglarlo.

El de Haskell, aunque más sofisticado, puede ser más críptico si no conocés sus fundamentos teóricos.


Haskell y Elm son dos lenguajes que muestran dos filosofías complementarias del mundo funcional.

Haskell te da un universo para explorar la abstracción.

Elm te da un terreno firme donde construir sin errores.

martes, 11 de noviembre de 2025

Type constraints en Elm


Elm es un lenguaje de tipado estático e inferencia fuerte: el compilador deduce los tipos automáticamente, pero también te permite declarar funciones genéricas que funcionan con más de un tipo.

Por ejemplo:


identity : a -> a

identity x = x


Esta función acepta cualquier tipo a.

Sin embargo, a veces queremos restringir qué tipos son válidos.

Ahí entran en juego los type constraints (restricciones de tipo).

En Elm, los type constraints permiten decir:

> “Este tipo genérico debe cumplir con ciertas propiedades (por ejemplo, ser comparable o numérico)”.


A diferencia de Haskell o Scala, Elm no tiene type classes, pero ofrece un pequeño conjunto de restricciones integradas que cubren los casos más comunes.

Elm define cuatro categorías de tipos con restricciones que podés usar en tus firmas de tipo:

  • number: Tipos que soportan operaciones aritméticas como Int, Float
  • comparable: Tipos que pueden ordenarse o compararse  como Int, Float, Char, String, tuples de comparables
  • appendable: Tipos que pueden concatenarse como String, List a
  • compappend:| Tipos que son a la vez comparable y appendable 


Podés restringir una función a operar solo sobre números:


sumar : number -> number -> number

sumar x y =

    x + y


Esto funciona con Int o Float, pero no con String.


Si necesitás ordenar o comparar valores:


menor : comparable -> comparable -> comparable

menor a b =

    if a < b then

        a

    else

        b


O incluso:


ordenar : List comparable -> List comparable

ordenar lista =

    List.sort lista


Cuando querés concatenar elementos:


concatenar : appendable -> appendable -> appendable

concatenar a b =

    a ++ b


Funciona con:


concatenar "Hola, " "mundo!"        -- "Hola, mundo!"

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


En Elm no se pueden definir tipos personalizados que sean comparable o appendable.

Por ejemplo, este tipo:


type alias Persona =

    { nombre : String, edad : Int }


No puede usarse en una función List.sort directamente.

Pero podés ordenarlo con una clave usando List.sortBy:


ordenarPorEdad : List Persona -> List Persona

ordenarPorEdad personas =

    List.sortBy .edad personas


O definir un criterio personalizado:


ordenarPorNombreDesc : List Persona -> List Persona

ordenarPorNombreDesc personas =

    List.sortWith (\a b -> compare b.nombre a.nombre) personas


Elm mantiene su sistema de tipos simple pero poderoso: no hay typeclasses ni herencia, pero sí restricciones útiles y seguras para los casos más comunes.


sábado, 1 de noviembre de 2025

Diseñando interfaces declarativas con Elm UI


Elm es conocido por su modelo funcional puro y su arquitectura confiable (el Elm Architecture).

Sin embargo, muchos desarrolladores se frustran al escribir HTML y CSS en el código de Elm tradicional.

Ahí entra Elm UI, una librería creada por Matthew Griffith que propone algo radical:

“Construir interfaces sin HTML ni CSS.”

mdgriffith/elm-ui reemplaza los módulos Html y Html.Attributes por un conjunto de tipos y funciones que representan elementos visuales y propiedades de diseño.

En vez de mezclar markup y estilo, describís la interfaz de forma estructurada y tipada.


Por ejemplo:


import Element exposing (..)

import Element.Font as Font


main =

    layout [] <|

        column [ spacing 20, centerX, centerY ]

            [ el [ Font.bold ] (text "Hola Elm UI!")

            , el [] (text "Una forma diferente de pensar las interfaces.")

            ]


Este código produce una interfaz centrada con dos textos.

Sin CSS, sin HTML — todo se define a través de funciones puras.


Elm UI se basa en unos pocos conceptos muy consistentes:

Element: Unidad visual principal, equivalente a un “nodo” del DOM

layout: Punto de entrada para renderizar la interfaz 

row, column: Contenedores para alinear elementos horizontal o verticalmente 

el: Envuelve un elemento simple (por ejemplo, texto o imagen) 

text: Muestra texto

Attribute msg: Propiedad de estilo o comportamiento (como `spacing`, `padding`, `centerX`, `Font.size`, etc.)


Veamos un ejemplo más completo: una pequeña tarjeta de usuario.


import Element exposing (..)

import Element.Background as Background

import Element.Border as Border

import Element.Font as Font


viewUser : Element msg

viewUser =

    el

        [ Background.color (rgb255 240 240 255)

        , Border.rounded 12

        , padding 16

        , width (px 250)

        , centerX

        ]

    <|

        column [ spacing 8 ]

            [ el [ Font.bold, Font.size 20 ] (text "Emanuel Goette")

            , el [ Font.color (rgb255 100 100 100) ] (text "Desarrollador de software")

            , el [] (text "🌎 Argentina")

            ]


Y en el main:


main =

    layout [] viewUser


El resultado: una tarjeta limpia, centrada, con esquinas redondeadas y colores suaves.

Todo expresado como funciones y tipos, sin estilos externos.


Elm UI funciona perfectamente dentro del patrón clásico Model, Update, View.

Ejemplo simple:


import Browser

import Element exposing (..)

import Element.Font as Font


type alias Model =

    { counter : Int }


init : Model

init =

    { counter = 0 }


type Msg

    = Increment

    | Decrement


update msg model =

    case msg of

        Increment ->

            { model | counter = model.counter + 1 }


        Decrement ->

            { model | counter = model.counter - 1 }


view model =

    layout []

        (column [ spacing 16, centerX, centerY ]

            [ el [ Font.size 24 ] (text ("Contador: " ++ String.fromInt model.counter))

            , row [ spacing 10 ]

                [ button "-" Decrement

                , button "+" Increment

                ]

            ]

        )


button label msg =

    el

        [ Background.color (rgb255 100 149 237)

        , Font.color (rgb255 255 255 255)

        , Border.rounded 6

        , paddingXY 16 8

        , mouseDown [ onClick msg ]

        ]

        (text label)


main =

    Browser.sandbox { init = init, update = update, view = view }


Un pequeño contador con estilo nativo y sin HTML.


Elm UI lleva la programación declarativa al diseño visual.

En lugar de pensar en “cómo” maquetar una interfaz, pensás en qué representa cada parte de ella.

“Elm UI te obliga a pensar en tu interfaz como un modelo de datos, no como una plantilla.”

Ideal para quienes buscan precisión, seguridad y coherencia en sus diseños.


Dejo link: 

https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/

https://ellie-app.com



viernes, 31 de octubre de 2025

Lamdera: el poder de Elm en el frontend y el backend


Elm es conocido por su modelo funcional puro, su tipado fuerte y la ausencia de errores en tiempo de ejecución.

Pero históricamente ha tenido una gran limitación: no puede ejecutarse en el backend.

Ahí entra Lamdera, un lenguaje derivado de Elm que propone algo ambicioso:

“Desarrollar aplicaciones fullstack en Elm, con estado compartido entre cliente y servidor, sin escribir ni una línea de JavaScript ni HTTP.”

Lamdera es un lenguaje y un entorno de ejecución creado por James Carlson, completamente compatible con Elm, pero que agrega:

  • Servidor integrado (no hace falta backend separado)
  • Comunicación automática cliente-servidor
  • Persistencia de estado
  • Despliegue con un solo comando


En otras palabras, Lamdera hace que Elm sea fullstack.

El compilador transforma tu código en una aplicación que corre tanto del lado del cliente como del servidor, sin necesidad de gestionar APIs, JSON o sincronización manual.

Lamdera extiende The Elm Architecture (TEA), pero con dos bucles de actualización:

uno en el cliente y otro en el servidor.

Ambos comparten el mismo modelo (Model) y el mismo tipo de mensaje (Msg), por lo que el tipado es consistente en toda la aplicación.

Un proyecto Lamdera se organiza con tres módulos principales:

  • Main.elm
  • Client.elm
  • Server.elm
Main.elm : Define los tipos compartidos entre cliente y servidor:


module Main exposing (..)


import Lamdera exposing (ClientProgram)

import Shared exposing (..)

import Client

import Server


main : ClientProgram Shared.Model Shared.Msg Shared.ToServer Shared.ToClient

main =

    Lamdera.client

        { init = Client.init

        , update = Client.update

        , view = Client.view

        , subscriptions = Client.subscriptions

        , onServerMsg = Client.onServerMsg

        , onClientConnect = Client.onClientConnect

        , onClientDisconnect = Client.onClientDisconnect

        }


Contiene los tipos comunes:


module Shared exposing (..)


type alias Model =

    { counter : Int }


type Msg

    = Increment

    | Decrement


type ToServer

    = SaveCounter Int


type ToClient

    = CounterUpdated Int


Client.elm : Maneja la vista y la interacción del usuario:


module Client exposing (..)


import Lamdera exposing (sendToServer)

import Element exposing (..)

import Shared exposing (..)


init : (Model, Cmd Msg)

init =

    ({ counter = 0 }, Cmd.none)


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        Increment ->

            let

                new = model.counter + 1

            in

            ( { model | counter = new }

            , sendToServer (SaveCounter new)

            )


        Decrement ->

            let

                new = model.counter - 1

            in

            ( { model | counter = new }

            , sendToServer (SaveCounter new)

            )


Server.elm: Contiene la lógica del servidor (persistencia, validación, etc.):


module Server exposing (..)


import Lamdera exposing (sendToClient)

import Shared exposing (..)


update : ToServer -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        SaveCounter value ->

            ( { model | counter = value }

            , sendToClient (CounterUpdated value)

            )


Con esto ya tenés una aplicación fullstack con comunicación cliente-servidor y estado sincronizado, todo en Elm.


Lamdera incluye herramientas propias:

  • lamdera run → ejecuta el servidor local y abre el navegador
  • lamdera check → analiza tipos y compilación
  • lamdera deploy → compila, sube y publica tu app
  • Dashboard online → permite gestionar el estado y logs


Lamdera todavía no es software libre (su runtime es cerrado, aunque gratuito para uso educativo y hobby).

Tampoco permite acceso directo a archivos o bases de datos externas (aunque se planea extender esto).

Aun así, su estabilidad es sorprendente: muchas aplicaciones en producción usan Lamdera desde hace años sin un solo error en runtime.

Lamdera lleva la promesa de Elm un paso más allá:

> “Sin errores, sin JavaScript, y ahora… sin backend.”


Con él, podés crear aplicaciones reactivas, tipadas y persistentes con una única base de código funcional pura.

Ideal para quienes aman Elm y quieren llevar sus principios hasta el servidor.


Dejo link: https://lamdera.com

https://lamdera.com/doc


martes, 28 de octubre de 2025

Unit Testing en Elm: confianza tipada desde el compilador hasta las pruebas


Elm ya es famoso por su compilador que evita errores en tiempo de ejecución, pero eso no significa que las pruebas no sean necesarias.

De hecho, los tests en Elm complementan su sistema de tipos, ayudándote a verificar la lógica de negocio, las transformaciones de datos y el comportamiento de tus funciones puras.

A diferencia de otros lenguajes donde los tests buscan prevenir errores de nulls, tipos o efectos secundarios, en Elm los tests sirven principalmente para:

  • Asegurar que una función devuelva el resultado esperado.
  • Comprobar que una actualización de modelo (en TEA) cambie el estado correctamente.
  • Validar transformaciones de datos o funciones puras de negocio.


Gracias a la pureza funcional, las pruebas en Elm son simples y predecibles.

El framework oficial de testing es elm-test.


Instalalo con:


npm install -g elm-test

elm-test init


Esto crea una carpeta:


tests/

 └── Example.elm


donde podrás escribir tus pruebas.


Supongamos que tenés una función en src/MathUtils.elm:


module MathUtils exposing (add)


add : Int -> Int -> Int

add a b =

    a + b


Podés crear un test en tests/MathUtilsTest.elm:


module MathUtilsTest exposing (tests)


import Expect

import Test exposing (..)

import MathUtils exposing (add)


tests : Test

tests =

    describe "Pruebas de MathUtils"

        [ test "Suma básica" <|

            \_ -> Expect.equal 4 (add 2 2)

        , test "Suma con negativos" <|

            \_ -> Expect.equal 0 (add 2 -2)

        ]


Ejecutalo con:

elm-test


y deberías ver algo como:


TEST RUN PASSED

Duration: 15 ms


Podés probar también las funciones update de tu arquitectura Elm.

Supongamos una aplicación simple que incrementa un contador:


type alias Model =

    { counter : Int }


type Msg

    = Increment


update : Msg -> Model -> Model

update msg model =

    case msg of

        Increment ->

            { model | counter = model.counter + 1 }


El test sería:


module CounterTest exposing (tests)


import Expect

import Test exposing (..)

import Main exposing (update, Model(..), Msg(..))


tests : Test

tests =

    describe "Update del contador"

        [ test "Incrementa el contador" <|

            \_ ->

                let

                    model = { counter = 0 }

                    updated = update Increment model

                in

                Expect.equal 1 updated.counter

        ]



Expect.notEqual – asegura que dos valores sean diferentes.

Expect.true / Expect.false – comprueba booleanos.

Expect.all – combina varios asserts sobre el mismo valor.

Fuzz tests – pruebas con datos aleatorios (muy útiles para funciones matemáticas o validaciones).


Ejemplo de fuzzing:


import Fuzz exposing (int)


fuzzTest : Test

fuzzTest =

    fuzz int "Suma con cero no cambia el número" <|

        \n -> Expect.equal n (add n 0)


El fuzzer genera cientos de casos aleatorios para asegurar que la propiedad siempre se cumple.

El sistema de tipos de Elm ya evita gran parte de los errores comunes, pero las pruebas unitarias llevan esa confianza un paso más allá. Te permiten documentar y verificar el comportamiento de tus funciones de forma declarativa, legible y segura.

domingo, 26 de octubre de 2025

Manejo de efectos en Elm: Tasks, Commands y Subscriptions


Elm es conocido por su promesa audaz: “sin errores en tiempo de ejecución”.

Pero, ¿cómo logra mantener esa pureza funcional incluso cuando necesita interactuar con el mundo real —hacer peticiones HTTP, leer el tiempo o escuchar eventos del navegador?

La respuesta está en su sistema de efectos controlados, manejados a través de tres conceptos clave:

  • Task
  • Cmd (Command)
  • Sub (Subscription)

En lenguajes imperativos, un efecto secundario (como imprimir en consola, hacer un fetch, o leer el reloj) puede suceder en cualquier parte del código.

En Elm, en cambio, toda función debe ser pura: dado el mismo input, siempre devuelve el mismo output.

Esto significa que no podés ejecutar efectos directamente dentro de tus funciones —en su lugar, los describís y Elm se encarga de ejecutarlos de forma controlada.

Un Cmd (Command) representa una acción que Elm debe realizar fuera del mundo puro, y que luego generará un mensaje (msg) cuando termine.


Ejemplo: hacer una solicitud HTTP.


import Http

import Json.Decode exposing (string)


type Msg

    = GotGreeting (Result Http.Error String)


fetchGreeting : Cmd Msg

fetchGreeting =

    Http.get

        { url = "https://api.example.com/hello"

        , expect = Http.expectString GotGreeting

        }


Este Cmd Msg no ejecuta la solicitud, solo le dice al runtime de Elm:

Por favor, hacé esta petición y cuando tengas el resultado, mandame un GotGreeting.

En tu update, procesás el resultado:


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        GotGreeting (Ok text) ->

            ({ model | greeting = text }, Cmd.none)

        GotGreeting (Err _) ->

            ({ model | greeting = "Error al conectar" }, Cmd.none)


Task: trabajos que pueden fallar o devolver un valor

Un Task es una descripción más general de un efecto que puede producir un resultado o un error.


Por ejemplo, si quisieras obtener el tiempo:


import Task

import Time exposing (Posix)


type Msg

    = GotTime Posix


getTime : Cmd Msg

getTime =

    Task.perform GotTime Time.now


Task.perform convierte un Task en un Cmd, para que pueda ser ejecutado por Elm.


El flujo sería:

  1. Time.now devuelve un Task Never Posix (una tarea que no falla y produce un tiempo).
  2. Task.perform lo transforma en un Cmd Msg.
  3. Elm ejecuta el comando y manda GotTime cuando termina.


Sub msg: escuchar eventos externos

Mientras que Cmd representa acciones que Elm inicia,

Sub representa cosas que suceden fuera y Elm escucha.

Por ejemplo, escuchar el paso del tiempo:


subscriptions : Model -> Sub Msg

subscriptions model =

    Time.every 1000 Tick


Cada segundo, Elm enviará un mensaje Tick, que procesás en tu update:


type Msg

    = Tick Posix


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        Tick time ->

            ({ model | currentTime = time }, Cmd.none)


Elm logra un equilibrio brillante:

  • mantiene la pureza funcional del lenguaje,
  • pero sin renunciar al mundo real.


Gracias a Task, Cmd y Sub, todo efecto está tipado, controlado y predecible, lo que permite que el compilador te ayude a manejar cada posible resultado.


Por eso, en Elm, incluso el caos del mundo exterior se maneja con elegancia funcional.


martes, 14 de octubre de 2025

Set en Elm: conjuntos funcionales, simples y seguros


En Elm, un Set representa un conjunto de valores únicos y ordenados, sin repeticiones y con operaciones típicas de teoría de conjuntos: unión, intersección, diferencia, etc.


Un Set se define como:


Set comparable


comparable: el tipo de los valores que puede contener (por ejemplo, Int, String, Char, etc.).


Todos los valores deben ser comparables, es decir, Elm debe poder ordenarlos.

Esto significa que no podés usar listas, records o funciones dentro de un Set.

Podés crearlo vacío o a partir de una lista:


import Set exposing (Set)


-- Conjunto vacío

numeros : Set Int

numeros = Set.empty


-- Desde una lista

pares : Set Int

pares = Set.fromList [2, 4, 6, 8, 10]


Los duplicados se eliminan automáticamente:


Set.fromList [1, 2, 2, 3]

-- Resultado: {1, 2, 3}


Set.member 4 pares

-- True


Set.member 5 pares

-- False


-- Agregar un elemento

paresActualizados =

    Set.insert 12 pares


-- Eliminar un elemento

paresFiltrados =

    Set.remove 8 paresActualizados


Recordá: todo es inmutable. Cada operación devuelve un nuevo Set, sin modificar el original.

Podés convertir un Set a lista:


Set.toList pares

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


Y viceversa:


Set.fromList [3,1,2]

-- {1,2,3}


Para aplicar transformaciones, primero lo convertís a lista y luego de vuelta:


paresDoblados =

    Set.fromList (List.map (\x -> x * 2) (Set.toList pares))


Elm ofrece las funciones clásicas de teoría de conjuntos:


Set.union: Unión de dos conjuntos

Set.union (Set.fromList [1,2]) (Set.fromList [2,3])  --> {1,2,3}


Set.intersect: Intersección 

Set.intersect (Set.fromList [1,2,3]) (Set.fromList [2,3,4])  --> {2,3}


Set.diff: Diferencia

Set.diff (Set.fromList [1,2,3]) (Set.fromList [2])  --> {1,3}


Set.isEmpty:¿Está vacío?

Set.isEmpty Set.empty --> True


Set.size: Cantidad de elementos

Set.size (Set.fromList [1,2,3]) --> 3



Gracias al sistema de tipos de Elm:

  • No hay null ni valores inesperados.
  • No podés mezclar tipos diferentes dentro del mismo Set.
  • Todas las operaciones son puras e inmutables.


Por ejemplo:


-- Esto da error de compilación

Set.fromList [1, "dos", 3]




miércoles, 8 de octubre de 2025

Array en Elm: eficiencia y acceso rápido a los datos


En Elm, las listas (List) son muy comunes, pero cuando necesitamos acceso rápido por índice, actualizaciones eficientes o manejar colecciones grandes, entra en juego Array.

Un Array en Elm es una estructura inmutable y eficiente que permite:

  • Acceder a elementos por índice (O(log n)).
  • Actualizar posiciones específicas sin mutar el original.
  • Convertirse fácilmente a listas (`List`) y viceversa.


Su módulo se importa así:


import Array exposing (Array)


Podés crearlo vacío o a partir de una lista:


-- Array vacío

vacio : Array Int

vacio = Array.empty


-- Desde una lista

numeros : Array Int

numeros = Array.fromList [10, 20, 30, 40]


Y también convertirlo nuevamente a lista:


Array.toList numeros

-- [10, 20, 30, 40]


Para obtener el valor en un índice determinado:


Array.get 2 numeros

-- Just 30


Observá que devuelve un Maybe, ya que el índice podría no existir.

Array.get 10 numeros

-- Nothing


-- Actualizar el valor en la posición 1

actualizado =

    Array.set 1 25 numeros


Array.toList actualizado

-- [10, 25, 30, 40]


Cada modificación devuelve un nuevo Array, sin alterar el original (inmutabilidad funcional).

Para agregar al final, usamos Array.push:


conNuevo =

    Array.push 50 numeros


Array.toList conNuevo

-- [10, 20, 30, 40, 50]


Y para quitar el último, usamos Array.pop:


sinUltimo =

    Array.pop conNuevo


Array.toList sinUltimo

-- [10, 20, 30, 40]


Podés recorrerlo de manera muy parecida a una lista:


Array.map (\x -> x * 2) numeros

-- {20, 40, 60, 80}


Y también filtrarlo o transformarlo:


Array.filter (\x -> x > 20) numeros

-- {30, 40}

Y hay más!! :


Array.length: Devuelve el tamaño          

Array.length numeros --> 4

                       |

Array.isEmpty: ¿Está vacío? 

Array.isEmpty Array.empty --> True


Array.initialize: Crea un array con una función

Array.initialize 5 (\i -> i * 2) --> {0,2,4,6,8}


Array.foldl/Array.foldr: Reduce el array

Array.foldl (+) 0 numeros --> 100                |


Veamos un ejemplo completo:


import Array exposing (Array)


main =

    let

        nums = Array.fromList [1,2,3,4]

        dobles = Array.map (\n -> n * 2) nums

        filtrados = Array.filter (\n -> n > 4) dobles

    in

    Array.toList filtrados

    

-- Resultado: [6,8]


Array en Elm te da:

  • Acceso rápido por índice
  • Inmutabilidad garantizada
  • Operaciones eficientes
  • Interoperabilidad con List


Usá List cuando pienses en secuencias y Array cuando necesites posiciones.


Dict en Elm: Diccionarios funcionales para datos ordenados


En Elm, un Dict (abreviatura de Dictionary) es una estructura de datos inmutable que almacena pares clave–valor, de forma ordenada y segura por tipos.

Un Dict se define como:


Dict comparable value


  • comparable → tipo de la clave (por ejemplo, `String`, `Int`, etc.)
  • value → tipo del valor asociado


Solo podés usar tipos comparables como claves, es decir, tipos con un orden definido (Int, String, Char, etc.).

No podés usar listas, records o funciones como claves.

Podés crear un diccionario vacío o con elementos iniciales:


import Dict exposing (Dict)


-- Vacío

usuarios : Dict Int String

usuarios = Dict.empty


-- Con valores

usuariosIniciales : Dict Int String

usuariosIniciales =

    Dict.fromList

        [ (1, "Ana")

        , (2, "Luis")

        , (3, "María")

        ]


Para buscar un valor por su clave:


Dict.get 2 usuariosIniciales

-- Resultado: Just "Luis"


Dict.get 5 usuariosIniciales

-- Resultado: Nothing


El resultado es un Maybe, lo que evita errores por claves inexistentes.

Podés manejarlo así:


case Dict.get 5 usuariosIniciales of

    Just nombre ->

        "Usuario encontrado: " ++ nombre

    Nothing ->

        "No existe ese usuario."


Insertar y eliminar elementos:


-- Agregar o actualizar

usuarios2 =

    Dict.insert 4 "Sofía" usuariosIniciales


-- Eliminar

usuarios3 =

    Dict.remove 2 usuarios2


Todo es inmutable: estas operaciones devuelven un nuevo Dict, no modifican el original.

Podés convertirlo a lista y trabajar con sus elementos:


Dict.toList usuariosIniciales

-- [(1, "Ana"), (2, "Luis"), (3, "María")]


List.map (\(id, nombre) -> nombre ++ " (" ++ String.fromInt id ++ ")")

    (Dict.toList usuariosIniciales)


O usar funciones específicas:


Dict.map (\_ nombre -> String.toUpper nombre) usuariosIniciales


Dict.member key dict: ¿Existe la clave?                   

Dict.size dict: Cantidad de elementos               

Dict.keys dict: Lista de claves                     

Dict.values dict: Lista de valores                    

Dict.filter pred dict: Filtra según condición              

Dict.union d1 d2: Une dos diccionarios                

Dict.merge: Mezcla con control sobre conflictos 

Dict en Elm es:

  • Inmutable
  • Ordenado por clave
  • Seguro (usa `Maybe` para búsquedas)
  • Funcional y expresivo


En Elm, un Dict es más que un mapa: es una garantía de orden, seguridad y pureza funcional.


lunes, 6 de octubre de 2025

Mónadas en Elm: Encadenando Cálculos con Elegancia


Las mónadas son un concepto central en la programación funcional. Aunque suenen complicadas, en Elm ya las usamos todo el tiempo sin darnos cuenta.

En términos simples: Una mónada es un funtor con una forma de encadenar operaciones que devuelven estructuras.

En Elm esto se hace con funciones como andThen (también llamada bind en otros lenguajes).

Maybe como mónada: Cuando trabajamos con valores opcionales (Maybe), podemos encadenar operaciones sin preocuparnos por el caso Nothing.


dividir : Int -> Int -> Maybe Int

dividir a b =

    if b == 0 then

        Nothing

    else

        Just (a // b)


calculo : Maybe Int

calculo =

    Just 100

        |> Maybe.andThen (\x -> dividir x 2)

        |> Maybe.andThen (\y -> dividir y 5)


-- Resultado: Just 10


Si en algún paso se produce un Nothing, toda la cadena devuelve Nothing automáticamente.


Result como mónada


Con Result, podemos propagar errores sin necesidad de escribir mucho código repetitivo:


parseEntero : String -> Result String Int

parseEntero s =

    case String.toInt s of

        Just n -> Ok n

        Nothing -> Err "No es un número"


invertir : Int -> Result String Float

invertir n =

    if n == 0 then

        Err "División por cero"

    else

        Ok (1 / toFloat n)


calculo : Result String Float

calculo =

    parseEntero "10"

        |> Result.andThen invertir


-- Resultado: Ok 0.1


Si el parseo falla, se corta la cadena con Err. Si no, se sigue con el siguiente cálculo.


List como mónada


Con listas, una mónada nos permite generar todas las combinaciones posibles de elementos:


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)]


Esto es equivalente a las list comprehensions en Haskell.


Resumen de funciones clave en Elm

  • Maybe.andThen → encadena operaciones opcionales.
  • Result.andThen → encadena operaciones que pueden fallar con error.
  • List.concatMap → encadena operaciones que generan más listas.


Todas siguen el mismo patrón monádico.

En Elm, aunque no hablemos directamente de “Mónadas” en la sintaxis, las usamos constantemente.