Translate

lunes, 7 de julio de 2025

Mapeo de Herencia en JPA


JPA ofrece 3 estrategias para mapear herencia:

  • @Inheritance(strategy = SINGLE_TABLE) : Todo se guarda en una sola tabla con una columna discriminadora. 
  • @Inheritance(strategy = JOINED): Se usa una tabla por clase, relacionadas por claves foráneas.        
  • @Inheritance(strategy = TABLE_PER_CLASS): Una tabla para cada clase, sin relaciones.                           

Veamos un ejemplo: Clase base y subclases

Vamos a usar la estrategia: SINGLE_TABLE.


package com.ejemplo.demo.entidad;

import jakarta.persistence.*;


@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name = "tipo_empleado", discriminatorType = DiscriminatorType.STRING)

public abstract class Empleado {


    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String nombre;


    // Getters y setters

}


@Inheritance(...): Define la estrategia de herencia.

@DiscriminatorColumn(...):  Crea una columna adicional (tipo_empleado) que almacena el tipo real de cada instancia.


📄 Gerente.java


package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

@DiscriminatorValue("GERENTE")

public class Gerente extends Empleado {


    private String departamento;


    // Getters y setters

}


📄 Desarrollador.java


package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

@DiscriminatorValue("DESARROLLADOR")

public class Desarrollador extends Empleado {


    private String lenguajeFavorito;


    // Getters y setters

}


Resultado en la base de datos


Se crea una única tabla empleado, con las columnas:

id, nombre, departamento, lenguajeFavorito y tipo_empleado (con valores como GERENTE, DESARROLLADOR)


Ventajas de SINGLE_TABLE

    • Más simple y rápida para consultas.

    • Ideal si la mayoría de los campos son comunes.

Desventajas

    • Muchos campos nulos en columnas que sólo usa una subclase.


Otras estrategias: 

    • JOINED: separa en tablas, y junta usando claves foráneas. Útil si los datos son muy distintos y no querés campos nulos.

    • TABLE_PER_CLASS: evita JOINs, pero no es tan usada. No permite relaciones polimórficas fácilmente.


domingo, 6 de julio de 2025

Relaciones entre Entidades con JPA


Aprender a modelar relaciones entre tablas en Java usando JPA, como por ejemplo:

  • @OneToMany (uno a muchos)

  • @ManyToOne (muchos a uno)

  • @OneToOne (uno a uno)

  • @ManyToMany (muchos a muchos)


En este ejemplo vamos a modelar una relación simple: Una Persona puede tener varias Direcciones.


package com.ejemplo.demo.entidad;


import jakarta.persistence.*;

import java.util.List;


@Entity

public class Persona {


@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String nombre;

@OneToMany(mappedBy = "persona", cascade = CascadeType.ALL, orphanRemoval = true)

private List<Direccion> direcciones;


// Constructores, getters y setters

}


Anotaciones clave:

@OneToMany(mappedBy = "persona"): Declara una relación de uno a muchos. Una persona puede tener varias direcciones.

  • mappedBy = "persona": indica que el atributo que mantiene la relación está del otro lado (Direccion.persona).
  • cascade = CascadeType.ALL: al guardar o eliminar una persona, se aplican los cambios también a sus direcciones.
  • orphanRemoval = true: elimina direcciones si se quitan de la lista.



Entidad Direccion:



package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

public class Direccion {


@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String calle;

private String ciudad;


@ManyToOne

@JoinColumn(name = "persona_id")

private Persona persona;


// Constructores, getters y setters

}



Anotaciones clave:

@ManyToOne: Cada dirección pertenece a una sola persona.

@JoinColumn(name = "persona_id"): Crea una columna en la tabla direccion llamada persona_id que actúa como clave foránea a la tabla persona.


Las listas deben ser inicializadas como new ArrayList<>() si se van a usar directamente.

Si no usás orphanRemoval, las direcciones quedan "huérfanas" si quitás una de la lista.


Un pequeño ejemplo para que vayamos incrementando nuestro conocimiento en JPA. 

viernes, 4 de julio de 2025

Tipos en Elm


Una de las principales ventajas de Elm es que, en la práctica, los usuarios no ven errores de ejecución. Esto es posible gracias a que el compilador de Elm puede analizar el código fuente rápidamente para ver cómo fluyen los valores a través del programa. Si un valor se usa de forma inválida, el compilador lo notifica con un mensaje de error intuitivo. Esto se denomina inferencia de tipos. El compilador determina qué tipo de valores entran y salen de todas las funciones.

El siguiente código define una función toFullName que extrae el nombre completo de una persona como una cadena:


toFullName person =

  person.firstName ++ " " ++ person.lastName


fullName =

  toFullName { fistName = "Hermann", lastName = "Hesse" }


Al igual que en JavaScript o Python, simplemente escribimos el código sin complicaciones. ¿Ves el error?

En JavaScript, el código equivalente genera "undefined Hesse". ¡Ni siquiera es un error! Con suerte, algún usuario te lo dirá cuando lo vea en acción. En cambio, el compilador de Elm simplemente revisa el código fuente y te dice:

-- TYPE MISMATCH ---------------------------------------------------------------

The argument to function `toFullName` is causing a mismatch.

6│   toFullName { fistName = "Hermann", lastName = "Hesse" }
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Function `toFullName` is expecting the argument to be:

    { …, firstName : … }

But it is:

    { …, fistName : … }

Hint: I compared the record fields and found some potential typos.

    firstName <-> fistName


Detecta que toFullName recibe el tipo de argumento incorrecto. Como indica la pista del mensaje de error, alguien escribió "fist" por error en lugar de "first".

Es fantástico tener un asistente para errores sencillos como este, pero es aún más valioso cuando tienes cientos de archivos y muchos colaboradores realizando cambios. No importa cuán grandes y complejas sean las cosas, el compilador de Elm comprueba que todo encaje correctamente basándose únicamente en el código fuente.

Cuanto mejor comprendas los tipos, más fácil te resultará el compilador. ¡Así que empecemos a aprender más!

miércoles, 2 de julio de 2025

¿Qué es Spring Data JPA? Parte 2

 


Veamos un ejemplo de entidad de jpa: 

📄 Persona.java


package com.ejemplo.demo.entidad;

import jakarta.persistence.*;


@Entity

public class Persona {

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String nombre;

    private String email;


    // Constructores

    public Persona() {}

    public Persona(String nombre, String email) {

        this.nombre = nombre;

        this.email = email;

    }


    // Getters y setters

    public Long getId() { return id; }

    public String getNombre() { return nombre; }

    public void setNombre(String nombre) { this.nombre = nombre; }

    public String getEmail() { return email; }

    public void setEmail(String email) { this.email = email; }

}


@Entity: Marca esta clase como una entidad JPA, lo que significa que será persistida en una tabla de la base de datos. El nombre de la tabla por defecto será el mismo que el nombre de la clase (persona en minúsculas).

@Id: Indica que el atributo id es la clave primaria de la entidad. Cada fila tendrá un valor único en esta columna.

@GeneratedValue(strategy = GenerationType.IDENTITY):  Especifica que el valor del campo id será generado automáticamente por la base de datos (usualmente como autoincremental).  Existen otras estrategias (AUTO, SEQUENCE, TABLE), pero IDENTITY es simple y efectiva para bases H2 o MySQL.


Repositorio

📄 PersonaRepository.java


package com.ejemplo.demo.repositorio;

import com.ejemplo.demo.entidad.Persona;

import org.springframework.data.jpa.repository.JpaRepository;


public interface PersonaRepository extends JpaRepository<Persona, Long> {

}


JpaRepository<Persona, Long>:  Es una interfaz de Spring Data que nos proporciona métodos CRUD listos para usar: findAll(), save(), deleteById(), etc.

Persona: el tipo de entidad

Long: el tipo de la clave primaria (id)

No se necesita ninguna anotación porque Spring escanea esta interfaz y genera una implementación automática.


Servicio

📄 PersonaService.java


@Service

public class PersonaService {

    // ...

}


@Service:  Marca esta clase como un componente de servicio, lo que permite que Spring la detecte y gestione como parte del contexto de la aplicación (inyección de dependencias, ciclo de vida, etc.).

Controlador

📄 PersonaController.java


@RestController

@RequestMapping("/personas")

public class PersonaController {

    // ...

}


    • @RestController:  Marca la clase como un controlador REST. Es equivalente a usar @Controller + @ResponseBody para cada método.

    • @RequestMapping("/personas"):  Define la ruta base de la API. Todos los métodos se expondrán bajo /personas.

    • @GetMapping, @PostMapping, @DeleteMapping: Indican qué método HTTP debe usarse para cada operación.

    • @RequestBody:  Indica que los datos del cuerpo del request (formato JSON) deben ser convertidos a un objeto Java.

    • @PathVariable: Extrae una variable de la URL, por ejemplo /personas/1 extrae el id = 1.


Archivo application.properties

spring.datasource.url=jdbc:h2:mem:testdb

spring.jpa.hibernate.ddl-auto=update

spring.h2.console.enabled=true


spring.jpa.hibernate.ddl-auto=update:  Le indica a JPA que genere automáticamente las tablas según nuestras entidades. Si la tabla no existe, la crea.


martes, 1 de julio de 2025

Forms en Elm


Ahora crearemos un formulario básico. Tiene un campo para tu nombre, un campo para tu contraseña y un campo para verificarla. También realizaremos una validación muy sencilla para comprobar si las contraseñas coinciden.

A continuación, incluí el programa completo:


-- Input a user name and password. Make sure the password matches.

--

-- Read how it works:

--   https://guide.elm-lang.org/architecture/forms.html

--


import Browser

import Html exposing (..)

import Html.Attributes exposing (..)

import Html.Events exposing (onInput)


-- MAIN

main =

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


-- MODEL

type alias Model =

  { name : String

  , password : String

  , passwordAgain : String

  }


init : Model

init =

  Model "" "" ""


-- UPDATE

type Msg

  = Name String

  | Password String

  | PasswordAgain String


update : Msg -> Model -> Model

update msg model =

  case msg of

    Name name ->

      { model | name = name }

    Password password ->

      { model | password = password }

    PasswordAgain password ->

      { model | passwordAgain = password }


-- VIEW

view : Model -> Html Msg

view model =

  div []

    [ viewInput "text" "Name" model.name Name

    , viewInput "password" "Password" model.password Password

    , viewInput "password" "Re-enter Password" model.passwordAgain PasswordAgain

    , viewValidation model

    ]


viewInput : String -> String -> String -> (String -> msg) -> Html msg

viewInput t p v toMsg =

  input [ type_ t, placeholder p, value v, onInput toMsg ] []


viewValidation : Model -> Html msg

viewValidation model =

  if model.password == model.passwordAgain then

    div [ style "color" "green" ] [ text "OK" ]

  else

    div [ style "color" "red" ] [ text "Passwords do not match!" ]



Esto es bastante similar a nuestro ejemplo de campo de texto, pero con más campos.

Siempre empiezo calculando el modelo. Sabemos que habrá tres campos de texto, así que sigamos con eso:


type alias Model =

  { name : String

  , password : String

  , passwordAgain : String

  }

Normalmente intento empezar con un modelo mínimo, quizás con un solo campo. Luego intento escribir las funciones de vista y actualización. Esto suele revelar que necesito añadir más elementos a mi modelo. Construir el modelo gradualmente de esta manera significa que puedo tener un programa funcional durante el proceso de desarrollo. Puede que aún no tenga todas las funciones, ¡pero casi las tiene!

A veces se tiene una idea bastante clara de cómo se verá el código básico de actualización. Sabemos que necesitamos poder cambiar nuestros tres campos, por lo que necesitamos mensajes para cada caso.


type Msg

  = Name String

  | Password String

  | PasswordAgain String


Esto significa que nuestra actualización necesita un caso para las tres variantes:


update : Msg -> Model -> Model

update msg model =

  case msg of

    Name name ->

      { model | name = name }

    Password password ->

      { model | password = password }

    PasswordAgain password ->

      { model | passwordAgain = password }


Cada caso utiliza la sintaxis de actualización de registros para garantizar que se transforme el campo correspondiente. Esto es similar al ejemplo anterior, pero con más casos.

Esta función de vista utiliza funciones auxiliares para hacer las cosas un poco más organizadas:


view : Model -> Html Msg

view model =

  div []

    [ viewInput "text" "Name" model.name Name

    , viewInput "password" "Password" model.password Password

    , viewInput "password" "Re-enter Password" model.passwordAgain PasswordAgain

    , viewValidation model

    ]


En ejemplos anteriores, usábamos input y div directamente. ¿Por qué dejamos de hacerlo?

Lo bueno de HTML en Elm es que input y div son funciones normales. Toman una lista de atributos y  una lista de nodos secundarios. Como usamos funciones normales de Elm, ¡tenemos todo el poder de Elm para ayudarnos a crear nuestras vistas! Podemos refactorizar código repetitivo y convertirlo en funciones auxiliares personalizadas. ¡Eso es exactamente lo que estamos haciendo aquí!


Así que nuestra función de vista tiene tres llamadas a viewInput:

viewInput : String -> String -> String -> (String -> msg) -> Html msg

viewInput t p v toMsg =

  input [ type_ t, placeholder p, value v, onInput toMsg ] []


Esto significa que escribir viewInput "text" "Name" "Bill" Name en Elm se convertiría en un valor HTML como <input type="text" placeholder="Name" value="Bill"> al mostrarse en pantalla.


La cuarta entrada es más interesante. Es una llamada a viewValidation:


viewValidation : Model -> Html msg

viewValidation model =

  if model.password == model.passwordAgain then

    div [ style "color" "green" ] [ text "OK" ]

  else

    div [ style "color" "red" ] [ text "Passwords do not match!" ]


Esta función primero compara las dos contraseñas. Si coinciden, se muestra texto verde y un mensaje positivo. Si no coinciden, se muestra texto rojo y un mensaje útil.

Estas funciones auxiliares empiezan a mostrar las ventajas de que nuestra biblioteca HTML sea código Elm normal. Podríamos incluir todo ese código en nuestra vista, pero crear funciones auxiliares es totalmente normal en Elm, incluso en el código de la vista. 

Records en Elm


Un registro puede contener muchos valores, y cada valor está asociado a un nombre.

Aquí se muestra un registro que representa al economista británico John A. Hobson:


> john =

|   { first = "John"

|   , last = "Hobson"

|   , age = 81

|   }

{ age = 81, first = "John", last = "Hobson" }


> john.last

"Hobson"


Hemos definido un registro con tres campos que contienen información sobre el nombre y la edad de John.

También puedes acceder a los campos del registro mediante una función de acceso a campos como esta:


> john = { first = "John", last = "Hobson", age = 81 }

{ age = 81, first = "John", last = "Hobson" }

> .last john

"Hobson"

> List.map .last [john,john,john]

["Hobson","Hobson","Hobson"]

>  


A menudo es útil actualizar valores en un registro:

> john = { first = "John", last = "Hobson", age = 81 }

{ age = 81, first = "John", last = "Hobson" }


> { john | last = "Adams" }

{ age = 81, first = "John", last = "Adams" }


> { john | age = 22 }

{ age = 22, first = "John", last = "Hobson" }


Si quisieras decir estas expresiones en voz alta, dirías algo como: "Quiero una nueva versión de John cuyo apellido sea Adams" o "John cuya edad sea 22".

Ten en cuenta que al actualizar algunos campos de John, creamos un registro completamente nuevo. No sobrescribe el existente. Elm optimiza este proceso compartiendo la mayor cantidad de contenido posible. Si actualizas uno de los diez campos, el nuevo registro compartirá los nueve valores sin cambios.

Una función para actualizar las edades podría verse así:


> celebrateBirthday person =

|   { person | age = person.age + 1 }

<function>

> john = { first = "John", last = "Hobson", age = 81 }

{ age = 81, first = "John", last = "Hobson" }

> celebrateBirthday john

{ age = 82, first = "John", last = "Hobson" }


¿Cuándo usar record en C#?


Desde C# 9, el lenguaje introdujo una nueva palabra clave: record. A simple vista parece una forma alternativa de declarar clases, pero su objetivo es mucho más específico: modelar datos inmutables que se comparan por valor.

Un record es un tipo de referencia que, a diferencia de las clases tradicionales, se compara por valor y está orientado a la inmutabilidad.


public record Person(string Name, int Age);


var p1 = new Person("Ada", 30);

var p2 = new Person("Ada", 30);


Console.WriteLine(p1 == p2); // True


Con clases normales (class), esta comparación daría false, porque se comparan las referencias.


Usá record cuando:

  • Te interesa la comparación por valor: Por ejemplo, para representar coordenadas, dinero, personas, productos, etc.
  • No necesitás mutabilidad. Los record son inmutables por defecto (aunque podés crear record class mutables, no es lo ideal).
  • Estás trabajando con código funcional o DDD. Es perfecto para modelar objetos de valor (value objects).
  • Querés usar with para copiar fácilmente con cambios:


var p3 = p1 with { Age = 31 };


 ¿Cuándo no usar record?

  • Cuando necesitás una identidad mutable (por ejemplo, una entidad con un Id que cambia de estado).
  • Cuando estás trabajando con frameworks como EF Core que esperan clases tradicionales para el mapeo.
  • Cuando necesitás herencia compleja (los record funcionan bien, pero pueden agregar confusión si no se usan con cuidado).


Desde C# 10, también existe record struct: un tipo por valor que se comporta como record, pero como struct.


public readonly record struct Coordinates(int X, int Y);


Ideal para representar datos livianos inmutables, como vectores o puntos.


Si estás modelando datos que no cambian, donde la igualdad semántica importa más que la identidad, y querés un código limpio, expresivo y seguro, record es la herramienta perfecta.


¿Qué es Spring Data JPA?


JPA (Java Persistence API) es una especificación estándar de Java para el mapeo objeto-relacional (ORM), que permite interactuar con bases de datos relacionales utilizando objetos Java.


Con JPA se pueden:

    • Definir clases Java como entidades persistentes (@Entity).

    • Especificar relaciones entre entidades (uno a muchos, muchos a muchos, etc.).

    • Realizar operaciones como insertar, actualizar, eliminar y consultar datos sin escribir SQL explícito.


JPA no es una implementación, sino una interfaz estándar. Las implementaciones más comunes son:

    • Hibernate (la más usada)

    • EclipseLink

    • OpenJPA


Spring Data JPA es un módulo de Spring que simplifica el uso de JPA:

    • Permite definir interfaces de repositorio sin necesidad de implementar los métodos CRUD.

    • Integra la capa de persistencia con el resto del ecosistema de Spring (inyección de dependencias, control transaccional, validaciones, etc.).

    • Facilita la creación de consultas con nombres de método (findByNombre, findByEmailContaining, etc.).

    • Soporta JPQL, SQL nativo y consultas dinámicas.


Spring Data JPA es una abstracción sobre JPA que reduce drásticamente el código necesario para acceder a la base de datos.


Ventajas de usar JPA con Spring

    • Menos código repetitivo: no es necesario implementar manualmente métodos CRUD.

    • Integración total con Spring: todo se gestiona mediante inyección de dependencias y configuración automática.

    • Consultas declarativas: se pueden crear consultas complejas usando el nombre del método.

    • Abstracción del SQL: permite trabajar con objetos sin escribir sentencias SQL directamente.

    • Gestión automática del contexto de persistencia: Spring gestiona transacciones, apertura y cierre de conexiones.

    • Compatible con múltiples bases de datos: MySQL, PostgreSQL, H2, Oracle, SQL Server, etc.


En el siguiente post vamos con algo más práctico. 


sábado, 28 de junio de 2025

El patrón Value Object… y cómo Ruby se lo salta cuando quiere


En el diseño orientado a objetos, un Value Object representa una entidad inmutable, comparada por valor y sin identidad propia (es decir, su identidad es el valor). Es un patrón común en lenguajes como Java, C# y Kotlin. 

Por ejemplo si necesitamos representar una fecha, dinero, fracciones, números complejos, etc. usaremos este patrón. 


Un Value Object tiene tres características esenciales:

  • Inmutabilidad: su estado no cambia después de ser creado.
  • Comparación por valor: dos objetos con los mismos atributos se consideran iguales.
  • Ausencia de identidad propia: no importa quién lo creó ni cuándo, solo importa su valor.


En C# con record, tenés inmutabilidad y comparación por valor automáticamente:


public record Money(decimal Amount, string Currency);


var a = new Money(10, "USD");

var b = new Money(10, "USD");


Console.WriteLine(a == b); // True


Y no podés modificar sus valores.


En Ruby, podés definir una clase que parezca un Value Object:


class Money

  attr_reader :amount, :currency


  def initialize(amount, currency)

    @amount = amount

    @currency = currency

  end


  def ==(other)

    amount == other.amount && currency == other.currency

  end

end


Hasta ahí todo bien, pero... todo es mutable


usd = Money.new(100, "USD")

usd.instance_variable_set(:@amount, 999) # ¡Booom!


Ruby no impide modificar los atributos internos con metaprogramación. Incluso podés cambiar el comportamiento de un único objeto:


usd.define_singleton_method(:amount) { 0 }


Este tipo de cosas rompen completamente la idea de inmutabilidad.

Entonces, ¿Cómo hacer que un VO en Ruby sea más seguro? No hay garantía total, pero hay algunas medidas:


class SafeMoney

  attr_reader :amount, :currency

  def initialize(amount, currency)

    @amount = amount.freeze

    @currency = currency.freeze

    freeze

  end


  def ==(other)

    amount == other.amount && currency == other.currency

  end

end


  • freeze impide modificaciones.
  • freeze también se aplica a los valores internos.
  • El objeto completo se congela con freeze.


Aun así... no es 100% a prueba de balas. Ruby confía en vos.

Ruby es expresivo, flexible y poderoso. Pero esa flexibilidad puede ser peligrosa cuando aplicás patrones pensados para lenguajes más rígidos.

¿Querés un Value Object en Ruby? Podés tener algo parecido, pero tené en cuenta que, la inmutabilidad en Ruby es un acto de fe... y freeze es tu mejor aliado.

Y por ultimo un recuerdo de cuando aprendi Ruby: Antes de Ruby 2.4, existían dos clases distintas para representar enteros: Fixnum (para enteros pequeños) y Bignum (para enteros grandes). Aunque eran objetos y permitían monkey patching, seguían siendo inmutables, y si se intentaba forzar una mutación real, el programa fallaba.


class Fixnum

  def mutate!

    self.replace(99)  # Esto explota

  end

end


5.mutate!

# => Error: can't modify frozen Integer (TypeError)


Incluso si se intentaba hacer algo como modificar self o reemplazar el contenido del número, Ruby lo impedía, ya que los enteros eran internamente inmutables y congelados. Esto confundía a quienes veían métodos con ! en otros objetos como String, donde sí había mutabilidad real.

Desde Ruby 2.4, Fixnum y Bignum se unificaron en Integer, y aunque el comportamiento de inmutabilidad se mantiene, ahora es más claro que los enteros no pueden ni deben ser mutados.

jueves, 26 de junio de 2025

Asignaciones a this en structs en C#


En C#, solemos pensar que this es una entidad inmutable dentro de un objeto: podés usarlo para leer, pero jamás para asignar. Eso es cierto… salvo que estés trabajando con un struct. Y ahí es donde empieza lo interesante.

En clases, intentar escribir this = otraInstancia; es un error de compilación. Pero en structs, esa línea es válida, y en algunos casos, incluso útil.

¿Por qué? Porque un struct es un tipo de valor. Cuando estás dentro de un método de instancia de un struct, this es una referencia a una copia de la instancia, y podés reasignarla.

Veamos un ejemplo básico


struct Punto

{

    public int X;

    public int Y;


    public void Reiniciar()

    {

        this = new Punto(0, 0); // reemplaza toda la instancia

    }

}


Este método Reiniciar reemplaza completamente el contenido del struct por uno nuevo.


¿Y esto para qué sirve? A veces, en vez de ir campo por campo, simplemente querés decir:

Ya fue, esta instancia no me sirve, la reescribo entera.


Por ejemplo:


struct Usuario

{

    public string Nombre;

    public string Email;


    public void Vaciar()

    {

        this = default; // equivalente a new Usuario()

    }

}


C# 9 introdujo record struct, y con eso también podemos usar el patrón with, lo que abre otras posibilidades:


public readonly record struct Coordenada(int X, int Y)

{

    public Coordenada Mover(int dx, int dy)

    {

        return this with { X = this.X + dx, Y = this.Y + dy };

    }

}


¿Y si no querés devolver una nueva instancia? Podés mutar así:


public record struct Contador(int Valor)

{

    public void Incrementar()

    {

        this = this with { Valor = Valor + 1 };

    }

}


  • Esto solo funciona en structs, no en clases.
  • Al reasignar this, perdés todo el estado anterior.
  • No es común en código idiomático de C#, así que usalo con intención y claridad.


La asignación a this en structs es uno de esos detalles de C# que sorprenden. No es algo que debas usar todo el tiempo, pero saber que existe puede ayudarte a escribir código más claro en ciertos contextos.

La próxima vez que necesites reiniciar por completo una instancia de un struct, acordate:

Sí, podés decir this = ... y nadie te lo va a impedir.

martes, 24 de junio de 2025

Text Fields en Elm


Vamos a crear una aplicación sencilla que invierte el contenido de un campo de texto.


import Browser

import Html exposing (Html, Attribute, div, input, text)

import Html.Attributes exposing (..)

import Html.Events exposing (onInput)


-- MAIN

main =

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


-- MODEL

type alias Model =

  { content : String

  }


init : Model

init =

  { content = "" }


-- UPDATE

type Msg

  = Change String


update : Msg -> Model -> Model

update msg model =

  case msg of

    Change newContent ->

      { model | content = newContent }


-- VIEW

view : Model -> Html Msg

view model =

  div []

    [ input [ placeholder "Text to reverse", value model.content, onInput Change ] []

    , div [] [ text (String.reverse model.content) ]

    ]


Este código es una ligera variación del ejemplo anterior. Se configura un modelo. Se definen algunos mensajes. Se indica cómo actualizar. Se crea la vista. La diferencia radica simplemente en cómo se completó este esqueleto. 

Siempre empiezo por adivinar cuál debería ser mi modelo. Sabemos que debemos registrar lo que el usuario escribe en el campo de texto. Necesitamos esa información para saber cómo representar el texto invertido. Así que lo hacemos así:


type alias Model =

  { content : String

  }

Esta vez representamos el modelo como un registro. El registro almacena la entrada del usuario en el campo de contenido.

¿para qué molestarse en tener un registro si solo contiene una entrada? ¿No se podría usar la cadena directamente? ¡Claro! Pero empezar con un registro facilita la adición de más campos a medida que nuestra aplicación se vuelve más compleja. Cuando llegue el momento en que necesitemos dos entradas de texto, tendremos que hacer muchos más ajustes.

Ya tenemos nuestro modelo, así que normalmente procedemos a crear una función de vista:


view : Model -> Html Msg

view model =

  div []

    [ input [ placeholder "Text to reverse", value model.content, onInput Change ] []

    , div [] [ text (String.reverse model.content) ]

    ]


Creamos un <div> con dos hijos. El hijo interesante es el nodo <input>, que tiene tres atributos:

  • placeholder es el texto que se muestra cuando no hay contenido.
  • value es el contenido actual de este <input>.
  • onInput envía mensajes cuando el usuario escribe en este nodo <input>.

Al escribir "bard", se generarían cuatro mensajes:

  1. Change "b"
  2. Change "ba"
  3. Change "bar"
  4. Change "bard"

Estos se incorporarían a nuestra función de actualización.

Solo hay un tipo de mensaje en este programa, por lo que nuestra actualización solo tiene que manejar un caso:


type Msg

  = Change String


update : Msg -> Model -> Model

update msg model =

  case msg of

    Change newContent ->

      { model | content = newContent }


Cuando recibimos un mensaje indicando que el nodo <input> ha cambiado, actualizamos el contenido de nuestro modelo. Por lo tanto, si escribiera "bard", los mensajes resultantes generarían los siguientes modelos:


  1. { content = "b" }
  2. { content = "ba" }
  3. { content = "bar" }
  4. { content = "bard" }

Necesitamos registrar esta información explícitamente en nuestro modelo; de lo contrario, no habría forma de mostrar el texto invertido en nuestra función de vista.


domingo, 22 de junio de 2025

Asignar a *this en C++: una técnica útil en structs


En C++, this representa una referencia al objeto actual. Aunque muchas veces lo usamos para acceder a miembros, también es válido asignarle un nuevo valor. Esto resulta especialmente útil en structs que funcionan como tipos de valor, cuando queremos resetear o actualizar todos los campos de golpe.

Veamos un ejemplo: 


#include <iostream>

#include <string>


struct Punto {

    int x, y;


    void reiniciar() {

        *this = Punto{0, 0};  // Reasignación completa del objeto

    }


    void mover(int dx, int dy) {

        *this = Punto{x + dx, y + dy};  // Reemplazo con una nueva instancia

    }

};


int main() {

    Punto p{5, 10};

    p.mover(2, -3);

    std::cout << "x: " << p.x << ", y: " << p.y << std::endl;


    p.reiniciar();

    std::cout << "x: " << p.x << ", y: " << p.y << std::endl;

}


¿Por qué usarlo?

  • Código más claro: Evita múltiples líneas asignando miembro por miembro.
  • Inmutable en espíritu: Te permite trabajar con una nueva instancia sin modificar campos individuales.
  • Ideal en structs livianos: Como los de tipo valor.


No todo lo que brilla es oro, para utilizar esto tenemos que tener en cuenta: 

  • Solo es válido en contextos donde la reasignación no genera efectos colaterales indeseados.
  • No debe usarse en clases con invariantes complejas, donde cambiar el estado parcial pueda llevar a errores sutiles.


viernes, 20 de junio de 2025

this = default en structs: un truco válido (y útil) que no conocías de C#


Si venís trabajando con C# desde hace un tiempo, probablemente sepas que this en un método de instancia no se puede modificar. Es una referencia al objeto actual, y es inmutable… ¿o no?

Bueno, hay una excepción sorprendente: en los constructores de structs, this puede ser reasignado. Así es: podemos hacer this = default; y reinicializar por completo una instancia.


¿Qué significa this = default;?


Cuando hacemos esto en un struct:


this = default;


Le estamos diciendo al compilador que queremos resetear todos los campos del struct a sus valores por defecto:

  • int, float, double → 0
  • bool → false
  • objetos → null
  • otros structs → default también


¿Por qué es válido?


Porque los structs en C# son tipos de valor, no de referencia como las class. En los constructores de un struct, el compilador permite esta asignación especial a this porque se trata de una variable local implícita, y su valor es mutable dentro del constructor.


¿Y por qué no se puede hacer en class?


Porque en una clase, this es una referencia al objeto actual, y C# no te permite cambiar esa referencia. Sería como tratar de asignar una nueva dirección de memoria a una variable inmutable.


this = default; es útil cuando:

  • Querés evitar código repetido asignando valores por defecto.
  • Querés cortar un constructor temprano y dejar el objeto en estado "vacío" o neutro.
  • Trabajás con structs grandes o generics, y querés evitar errores al inicializar manualmente cada campo.


this = default puede parecer extraño al principio, pero es una herramienta válida y muy útil para inicializar structs de manera limpia y segura. No es magia, es C# aprovechando las reglas especiales de los tipos de valor.


jueves, 19 de junio de 2025

Java 25 integra Compact Object Headers (JEP 519)

 


Java 25, la próxima versión LTS, incorpora de forma nativa el soporte para Compact Object Headers mediante la JEP 519. Esta característica, que era experimental en Java 24, ahora se promueve como una funcionalidad oficial del VM, lo que implica grandes beneficios sin necesidad de modificar el código existente.

¿Qué son los Compact Object Headers?

En HotSpot (el JVM de referencia), cada objeto en memoria tiene un cabecera (header) que contiene meta información:

  • Un mark word (incluye bits para GC, monitoreo y hashcode)
  • Un class word (referencia comprimida a la clase del objeto)

Hasta ahora, este header ocupa 96 bits (12 bytes), lo cual significa un sobrecoste significativo —sobre todo en aplicaciones con muchos objetos de pequeño tamaño.


¿Qué cambia con Compact Object Headers?


Reducción del header a 64 bits (8 bytes), manteniendo todo lo necesario:

  • Mark word
  • Class pointer (comprimido a 22 bits)
  • Campos reservados para GC y futuros proyectos como Valhalla 

El ahorro en memoria puede llegar al 30 % de CPU y 20 % menos uso de heap en aplicaciones que crean muchos objetos pequeños. Benchmarks como SPECjbb2015 muestran mejoras de 22 % en heap y 8 % en CPU, además de menor frecuencia de GC.

Amazon ya lo ha probado en centenas de servicios, validando caídas de hasta 30 % en consumo CPU y 15 % menos GC, especialmente en colectores G1 y Parallel.


¿Cómo habilitarlo?


En Java 25 basta con agregar el siguiente flag al iniciar tu aplicación:


java -XX:+UseCompactObjectHeaders -jar mi-app.jar


Sin necesidad del flag -XX:+UnlockExperimentalVMOptions, ni cambiar una sola línea de código 


¿Por qué es relevante?

  • Menor consumo de memoria: ideal para microservicios, Kubernetes, contenedores o entornos edge.
  • Mejor rendimiento: los objetos más pequeños mejoran la localidad de caché y reducen cargas de GC.
  • Implementación sencilla: es transparente y compatible con código existente, sin riesgo ni esfuerzo extra.


Compact Headers es parte de Project Lilliput, cuyo objetivo es reducir drásticamente el overhead de los headers de objetos:

  • JDK 22 introdujo estructura para monitores de objetos
  • JEP 450 en Java 24 activó Compact Headers en modo experimental
  • Ahora, JEP 519 en Java 25 los integra como funcionalidad oficial 


Esto también adelanta compatibilidad con futuras mejoras como Valhalla (clases de valor) y Project Panama.


Recomendaciones prácticas

  • Activá en entornos de desarrollo o staging y monitorizá el uso de memoria y CPU.
  • En entornos de producción, el flag puede reducir costes operativos y mejorar densidad en la nube.
  • Revisá que no actives `-XX:-UseCompressedClassPointers` o locking legado, ya obsoletos en Java 25.
  • Esperá mejoras similares en plataformas x64 y AArch64; ZGC en x64 aún está en progreso


Java 25 (LTS) ofrece ahora una forma sencilla de optimizar memoria y rendimiento con un solo parámetro. Compact Object Headers representan una mejora tangible para cualquier proyecto moderno que genere muchos objetos. Es una mejora que conviene activar y medir desde ya.

Dejo link: 

https://www.infoq.com/news/2025/06/java-25-compact-object-headers/


martes, 17 de junio de 2025

Otra vez, la Arquitectura Elm

 Se acuerdan de este post, puede que te resulte más fácil ver cómo encajan en el diagrama que vimos en otro post anterior:


Elm comienza mostrando el valor inicial en pantalla. A partir de ahí, entras en este bucle:

  1. Esperar la entrada del usuario.
  2. Enviar un mensaje para actualizar.
  3. Producir un nuevo modelo.
  4. Llamar a la vista para obtener el nuevo HTML.
  5. Mostrar el nuevo HTML en pantalla.
  6. ¡Repetir!

Esta es la esencia de la arquitectura Elm. Cada ejemplo que veamos a partir de ahora será una ligera variación de este patrón básico.


lunes, 16 de junio de 2025

¿Qué es fixed en C#?


La palabra clave fixed en C# se usa en dos contextos, ambos relacionados con el trabajo de bajo nivel y el manejo de memoria no administrada (unsafe code).


Para obtener un puntero fijo a una variable administrada

En C#, los objetos administrados pueden moverse en memoria (por el Garbage Collector). Si queremos trabajar con punteros (como en C o C++), necesitamos asegurarnos de que el objeto no se mueva durante el uso del puntero.

Ahí entra fixed, le indica al runtime que fije el objeto en memoria.


unsafe 

{

    int[] numeros = { 1, 2, 3, 4 };


    fixed (int* p = numeros)

    {

        for (int i = 0; i < 4; i++)

        {

            Console.WriteLine(*(p + i));

        }

    }

}


  • fixed bloquea el arreglo numeros en memoria.
  • int* p es un puntero al primer elemento.
  • Mientras esté dentro del bloque fixed, numeros no se moverá.


Para fijar buffers dentro de structs fixed size buffer

C# permite declarar buffers de tamaño fijo dentro de structs cuando estás trabajando en código unsafe.


unsafe struct MiBuffer

{

    public fixed byte datos[10];

}


  • Aquí datos es un array de 10 bytes directamente dentro del struct.
  • Es muy usado cuando interoperás con código nativo o estructuras de bajo nivel.


Cosas importantes:

  • Solo se puede usar fixed dentro de código marcado como unsafe.
  • Necesitás habilitar la opción /unsafe en el compilador o el flag en el proyecto.
  • Ayuda a interoperar con APIs nativas, como Win32, o trabajar con bloques de memoria directamente.
  • No es algo común en código de negocio habitual en C#, pero sí muy usado en interop, performance crítica o trabajo con hardware.



Arrow Function vs Function en JavaScript


En JavaScript tenemos dos formas principales de declarar funciones:


// Function clásica

function sumar(a, b) {

  return a + b;

}


// Arrow function

const sumar = (a, b) => a + b;


Aunque pueden parecer equivalentes, tienen diferencias importantes:

this léxico:

  • Function clásica: el this depende de cómo se invoque la función.
  • Arrow function: el this se captura del contexto donde fue definida.


const obj = {

  numero: 42,

  funcionClasica: function() {

    console.log(this.numero);  // 42

  },

  arrowFunction: () => {

    console.log(this.numero);  // undefined (o distinto según el contexto externo)

  }

}


Las arrow functions no tienen su propio this.

Las arrow functions no tienen arguments:


function funcionClasica() {

  console.log(arguments);

}


const arrowFunction = () => {

  console.log(arguments); // ReferenceError

}


Si necesitas los argumentos en arrow function, podés usar parámetros rest (...args).


Las arrow functions no pueden ser constructores:


No podés hacer new sobre una arrow function.


function Persona() {}

const PersonaArrow = () => {};


new Persona(); // OK

new PersonaArrow(); // TypeError


Las arrow functions no tienen prototype:


console.log(Persona.prototype); // {}

console.log(PersonaArrow.prototype); // undefined


Las arrow functions tienen una sintaxis más compacta. Ideal para callbacks, funciones de orden superior o programación funcional:


const numeros = [1, 2, 3];

const dobles = numeros.map(n => n * 2);


Hoisting:

  • Las function declarations se elevan (hoisting).
  • Las arrow functions (al ser expresiones) no:


sumar(2,3); // OK

function sumar(a,b){ return a + b; }


sumarArrow(2,3); // TypeError: sumarArrow is not a function

const sumarArrow = (a,b) => a + b;


Mejor en callbacks, no siempre en métodos:


Por su manejo de this, las arrow functions suelen ser preferidas en callbacks, pero no tanto para métodos de objetos o clases.


Regla rápida: usa arrow functions cuando quieras mantener el this del contexto externo (sobre todo en callbacks). Usa functions clásicas cuando necesites un constructor, arguments, o una función completamente autónoma.


domingo, 15 de junio de 2025

¿Por qué C++ necesita punteros para implementar polimorfismo?


En muchos lenguajes de programación orientados a objetos, como Java, C#, Python o Ruby, el polimorfismo funciona de forma "natural" al manejar los objetos por referencia. Sin embargo, cuando trabajamos con C++, rápidamente notamos un detalle que desconcierta a muchos: para obtener polimorfismo dinámico necesitamos punteros (o referencias). ¿Por qué ocurre esto? Vamos a desglosarlo.

El polimorfismo permite invocar métodos sobre objetos de clases derivadas a través de una referencia o puntero a la clase base. El método invocado es el de la clase real del objeto, no necesariamente el de la clase base.

En C++, esto se logra declarando métodos virtual en la clase base:


class Animal {

public:

    virtual void hablar() {

        std::cout << "Soy un animal" << std::endl;

    }

};


class Perro : public Animal {

public:

    void hablar() override {

        std::cout << "Guau" << std::endl;

    }

};


Ahora viene la parte clave.


Animal a = Perro();

a.hablar();  // Imprime: "Soy un animal"


A pesar de haber creado un Perro, el método hablar de Animal es el que se llama. ¿Por qué?

Porque en esta asignación:


Animal a = Perro();


se produce slicing ("corte de objeto"). Solo la parte Animal del Perro es copiada en el objeto a. El comportamiento dinámico queda perdido.


Perro p;

Animal* a = &p;

a->hablar();  // Imprime: "Guau"


Ahora sí, el método correcto se invoca. ¿Qué cambió? Simple: no hay slicing. Estamos trabajando con la dirección de memoria del objeto original, donde toda la jerarquía de clases está intacta.

La razón está en el modelo de objetos de C++:

  • C++ permite que los objetos vivan por valor. Si asignamos Animal a = Perro(), se copia sólo la parte visible desde Animal. Las partes de Perro no existen en esa copia.
  • Los lenguajes como Java o C# manejan los objetos siempre por referencia; nunca se copia el objeto en una asignación como `Animal a = new Perro()`. Por eso ahí el slicing no ocurre.


Cuando usamos punteros o referencias en C++:

  • No se copia el objeto.
  • Se mantiene la dirección al objeto original.
  • El compilador puede usar el vtable (tabla virtual) para despachar la llamada al método correcto.

Una de las ventajas de C++ es que te permite elegir:

  • Si quieres performance máxima (sin polimorfismo), puedes trabajar por valor.
  • Si quieres polimorfismo dinámico, debes trabajar por puntero o referencia.


Este control es una de las razones por las cuales C++ sigue siendo muy usado en sistemas embebidos, motores de juego, drivers, etc.

C++ también permite otras formas de polimorfismo (estático), como el basado en plantillas (CRTP, SFINAE, concepts en C++20), donde no necesitamos punteros porque el binding ocurre en tiempo de compilación.


miércoles, 11 de junio de 2025

Ejemplo de una Aplicación en Elm


Nuestro primer ejemplo es un contador que se puede incrementar o decrementar:


-- Press buttons to increment and decrement a counter.

--

-- Read how it works:

--   https://guide.elm-lang.org/architecture/buttons.html

--



import Browser

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

import Html.Events exposing (onClick)




-- MAIN

main =

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




-- MODEL

type alias Model = Int

init : Model

init =  0




-- UPDATE

type Msg  = Increment  | Decrement


update : Msg -> Model -> Model

update msg model =

  case msg of

    Increment ->  model + 1

    Decrement ->  model - 1


-- VIEW

view : Model -> Html Msg

view model =  div []

    [ button [ onClick Decrement ] [ text "-" ]

    , div [] [ text (String.fromInt model) ]

    , button [ onClick Increment ] [ text "+" ]

    ]


Ahora que has explorado un poco el código, puede que tengas algunas preguntas. ¿Qué hace el valor principal? ¿Cómo se integran las diferentes partes? Analicemos el código y hablemos de ello.

El código utiliza anotaciones de tipo, alias de tipo y tipos personalizados. El objetivo de esta sección es familiarizarse con la arquitectura de Elm, así que no los abordaremos hasta más adelante. 

El main es especial en Elm. Describe lo que se muestra en pantalla. En este caso, inicializaremos nuestra aplicación con el valor init, la función de vista mostrará todo en pantalla y la entrada del usuario se introducirá en la función de actualización. Considérelo la descripción general de nuestro programa.

El modelado de datos es fundamental en Elm. El objetivo del modelo es capturar todos los detalles de la aplicación como datos.

Para crear un contador, necesitamos registrar un número que sube y baja. Esto significa que nuestro modelo es muy pequeño esta vez:


type alias Model = Int


Solo necesitamos un valor entero para registrar el conteo actual. Podemos verlo en nuestro valor inicial:


init : Model

init =  0


El valor inicial es cero y aumentará y disminuirá a medida que las personas presionen diferentes botones.

Tenemos un modelo, pero ¿cómo lo mostramos en pantalla? Esa es la función de vista:


view : Model -> Html Msg

view model =

  div []

    [ button [ onClick Decrement ] [ text "-" ]

    , div [] [ text (String.fromInt model) ]

    , button [ onClick Increment ] [ text "+" ]

    ]


Esta función toma el Modelo como argumento. Genera HTML. Por lo tanto, indicamos que queremos mostrar un botón de decremento, el conteo actual y un botón de incremento.


Observa que tenemos un controlador "onClick" para cada botón. Este indica que, al hacer clic, se genera un mensaje. El botón "+" genera un mensaje de incremento. ¿Qué es y adónde va? ¡A la función de actualización!

La función de actualización describe cómo cambiará nuestro modelo con el tiempo.


Definimos dos mensajes que podría recibir:


type Msg = Increment | Decrement


A partir de ahí, la función de actualización simplemente describe qué hacer cuando recibes uno de estos mensajes.


update : Msg -> Model -> Model

update msg model =

  case msg of

    Increment ->

      model + 1

    Decrement ->

      model - 1


Si recibe un mensaje de Incremento, incrementa el modelo. Si recibe un mensaje de Decremento, lo decrementa.


Así que, cada vez que recibimos un mensaje, lo ejecutamos mediante la función de actualización para obtener un nuevo modelo. Luego, llamamos a la vista para determinar cómo mostrar el nuevo modelo en pantalla. ¡Y luego repetimos! La entrada del usuario genera un mensaje, actualiza el modelo, lo visualiza en pantalla, etc.





martes, 10 de junio de 2025

new vs override en C#: ¿cuál es la diferencia y cuándo usar cada uno?



Cuando trabajamos con herencia en C#, dos palabras clave aparecen para definir comportamiento en clases derivadas: new y override. A primera vista pueden parecer similares, pero representan intenciones diferentes. Entenderlas bien puede evitarte muchos errores.

En C# una clase puede heredar miembros (métodos, propiedades, etc.) de otra. ¿Pero qué pasa si queremos cambiar el comportamiento de un método heredado?

Ahí entran en juego override y new.

Cuando queremos modificar el comportamiento de un método heredado, usámos override.


class Animal

{

    public virtual void Hablar()

    {

        Console.WriteLine("Hace un sonido");

    }

}


class Perro : Animal

{

    public override void Hablar()

    {

        Console.WriteLine("Guau");

    }

}


Y ahora mirá esto:


Animal a = new Perro();

a.Hablar(); // Imprime "Guau"


Con override, el comportamiento depende del tipo real del objeto, no del tipo de la variable. Esto se llama polimorfismo.


En cambio, new oculta el método de la clase base. No lo reemplaza a nivel del sistema de tipos.


class Animal

{

    public void Hablar()

    {

        Console.WriteLine("Hace un sonido");

    }

}


class Gato : Animal

{

    public new void Hablar()

    {

        Console.WriteLine("Miau");

    }

}


Probá esto:

Animal a = new Gato();

a.Hablar(); // Imprime "Hace un sonido"


Porque a es del tipo Animal, y el método Hablar() de Gato no lo reemplazó: solo lo ocultó. Pero:


Gato g = new Gato();

g.Hablar(); // Imprime "Miau"


¿cuándo uso new?

  • Cuando querés ocultar intencionalmente un método de la clase base, pero sin reemplazarlo para el sistema de tipos.
  • Generalmente se evita, porque puede causar confusión. Se usa cuando no podés cambiar la clase base (por ejemplo, una librería externa) pero necesitás un método con el mismo nombre y diferente comportamiento.


¿Qué pasa si no usás ni new ni override? El compilador mostrará una advertencia si detecta que estás ocultando un miembro sin decirlo explícitamente.


// Esto compila, pero da warning:

public void Hablar() { ... } // Oculta un método de la base


Siempre que ocultes un miembro heredado, C# quiere que lo declares con new o override, para que tu intención sea clara.

domingo, 8 de junio de 2025

La Arquitectura Elm

La Arquitectura Elm es un patrón para diseñar programas interactivos, como aplicaciones web y juegos.

Esta arquitectura parece surgir de forma natural en Elm. En lugar de que alguien la inventara, los primeros programadores de Elm seguían descubriendo los mismos patrones básicos en su código. ¡Era un poco inquietante ver a gente terminar con código bien diseñado sin planificación previa!

Así que la Arquitectura Elm es sencilla en Elm, pero útil en cualquier proyecto front-end. De hecho, proyectos como Redux se han inspirado en la Arquitectura Elm, así que quizás ya hayas visto derivados de este patrón. La cuestión es que, incluso si aún no puedes usar Elm en el trabajo, obtendrás mucho de su uso e internalización.

Los programas Elm siempre se ven así:


El programa Elm produce HTML para mostrar en pantalla y luego el ordenador envía mensajes de respuesta sobre lo que está sucediendo. "¡Han pulsado un botón!"

Pero ¿qué sucede dentro del programa Elm? Siempre se divide en tres partes:

  • Modelo: el estado de la aplicación
  • Vista: una forma de convertir el estado a HTML
  • Actualización: una forma de actualizar el estado según los mensajes

Estos tres conceptos son la base de la arquitectura Elm.


jueves, 5 de junio de 2025

Proxy en javascript


En JavaScript moderno hay varias formas de observar cambios en objetos, pero la más directa y nativa es usando Proxy, que te permite interceptar y reaccionar a operaciones sobre un objeto, como modificar una propiedad.


Veamos un ejemplo:


function observeObject(obj, callback) {

  return new Proxy(obj, {

    set(target, prop, value) {

      const oldValue = target[prop];

      target[prop] = value;


      // Ejecutamos el callback cuando se modifica algo

      callback(prop, value, oldValue);


      return true;

    }

  });

}


// Ejemplo de uso

const original = { nombre: 'Juan', edad: 30 };


const observado = observeObject(original, (prop, newVal, oldVal) => {

  console.log(`Propiedad '${prop}' cambió de ${oldVal} a ${newVal}`);

});


observado.nombre = 'Ana';   // Se dispara el callback

observado.edad = 31;        // También se dispara


Me gusto mucho, es simple y puede venir al pelo para ciertas ocaciones, pero debemos de tener en cuenta que : 

  • Solo observa propiedades directas, no anidadas. Si querés observar objetos dentro del objeto, hay que aplicar recursivamente el Proxy.
  • No detecta borrados (como delete obj.prop) a menos que también implementes el deleteProperty trap.
  • No funciona con arrays de forma intuitiva si hacés push, pop, etc., salvo que interceptes esos métodos.