Translate

miércoles, 10 de diciembre de 2025

Inicialización uniforme en C++


Desde C++11, C++ incorporó la inicialización uniforme, una forma unificada de inicializar variables, objetos y contenedores usando llaves {}.

Veamos un ejemplo: 


#include <iostream>

#include <vector>


int main() {

    int x{10};                     // variable

    std::vector<int> v{1, 2, 3, 4}; // lista de inicialización


    for (int n : v)

        std::cout << n << " ";

}


Salida:

1 2 3 4


Ventajas:

  • Evita conversiones implícitas peligrosas.
  • Funciona con cualquier tipo de dato.
  • Es más coherente y clara que las formas anteriores (=, ()).


Por ejemplo, esto no compila porque perdería precisión:


int n{3.5}; // error: pérdida de información


Mientras que con = sí lo haría (truncando el valor).

Por eso, la inicialización uniforme hace el código más seguro y predecible.


jueves, 4 de diciembre de 2025

Desafíos, límites y futuro de la IA generativa


Aunque las inteligencias artificiales generativas parecen “mágicas”, están lejos de ser perfectas.

Entender sus limitaciones, riesgos y posibilidades es clave para usarlas de forma responsable y aprovechar su verdadero potencial.

Uno de los mayores desafíos de los modelos generativos es que pueden inventar información con total confianza. Esto se conoce como una alucinación.

Por ejemplo, un modelo puede afirmar que un autor escribió un libro inexistente, o que una función de Python se llama de una forma incorrecta.


¿Por qué ocurre? Porque los modelos no “saben” cosas; predicen patrones.

Si un conjunto de palabras “suena correcto” según su entrenamiento, lo dirán, aunque no sea verdad.

Los modelos no distinguen entre lo probable y lo verdadero.

Por eso, cada vez más investigaciones buscan reducir las alucinaciones mediante:

  • integración con bases de conocimiento verificables,
  • modelos híbridos (razonamiento simbólico + redes neuronales),
  • y retroalimentación constante con información actualizada.


Los modelos aprenden del lenguaje humano… y el lenguaje humano está lleno de sesgos: culturales, políticos, de género, raciales, etc.

Por eso, un modelo puede reflejar o amplificar esos sesgos si no se controla cuidadosamente.

Las empresas e instituciones que desarrollan IA trabajan con:

  • equipos de ética y auditorías externas,
  • filtrado de datos para reducir prejuicios,
  • y técnicas como RLHF para mejorar la alineación con valores humanos.


Aun así, es un problema abierto:

> ¿cómo definimos lo que es “ético” o “correcto” en contextos culturales tan distintos?


Otro debate importante gira en torno a de dónde provienen los datos de entrenamiento.

Muchos modelos se entrenaron con grandes cantidades de texto de Internet, lo que plantea preguntas como:

  • ¿Quién es dueño del contenido generado?
  • ¿Puede un modelo “aprender” de obras con copyright?
  • ¿Qué pasa si memoriza datos sensibles?


Por eso, surgen nuevos enfoques:

  • entrenamientos con datos privados o sintéticos,
  • modelos open source auditables,
  • y regulaciones en camino, como la AI Act en la Unión Europea.


Entrenar un modelo generativo grande puede costar millones de dólares y consumir enormes cantidades de energía.

Por ejemplo, un solo entrenamiento puede requerir miles de GPU durante semanas.


Esto llevó al desarrollo de:

  • modelos más pequeños y eficientes (como LLaMA, Mistral o Phi),
  • técnicas de compresión y cuantización,
  • y estrategias de entrenamiento más ecológicas, como el sparse training o distillation.


El futuro apunta a modelos más sostenibles y distribuidos, accesibles incluso para equipos pequeños o dispositivos personales.


Uno de los mayores malentendidos es pensar que la IA viene a reemplazar a las personas.

En realidad, los mejores resultados se logran cuando humanos e IA trabajan juntos.


Diseñadores, programadores, escritores, docentes y científicos ya usan IA como:

  • asistente de ideas,
  • generador de borradores,
  • corrector o analista,
  • y herramienta de simulación o exploración creativa.

La IA amplifica la inteligencia humana, no la sustituye.


En los próximos años veremos una expansión hacia modelos:

  • multimodales completos, capaces de entender y generar texto, imagen, audio y video de forma unificada;
  • razonadores, que combinen generación con pensamiento lógico y planificación;
  • y personales, ajustados a nuestros hábitos, tono y estilo de comunicación.


Todo esto impulsará nuevas disciplinas como: AI Engineering, Prompt Design, AI Safety y Cognitive AI.


La inteligencia artificial generativa es una herramienta poderosa, pero también un espejo: refleja nuestras virtudes y nuestros límites como sociedad.


Su desarrollo plantea una pregunta fundamental:

> ¿Queremos máquinas que hablen como nosotros…

> o que piensen junto a nosotros?


El futuro dependerá de cómo respondamos a esa pregunta hoy.

Cómo se ajustan y mejoran los modelos generativos


Hay una pregunta clave que todavía no respondimos:

¿Cómo pasa un modelo de simplemente “predecir palabras” a comportarse como un asistente conversacional capaz de seguir instrucciones, responder con criterio o incluso tener “personalidad”?

La respuesta está en una serie de procesos que ocurren después del entrenamiento base, conocidos como fine-tuning, instruction tuning y RLHF (Reinforcement Learning from Human Feedback).

Estos pasos son los que transforman un modelo genérico en algo útil, amigable y confiable.

Durante el entrenamiento base, el modelo aprende cómo funciona el lenguaje: gramática, semántica, relaciones, contexto.

Pero todavía no sabe qué tipo de comportamiento esperamos de él.

Por ejemplo, un modelo base podría responder:

> “No sé quién sos ni por qué me hablás así.”

> cuando le pedimos algo tan simple como “Explicame qué es la fotosíntesis.”


Por eso, se aplica una segunda etapa de entrenamiento: el fine-tuning.

El fine-tuning (ajuste fino) consiste en volver a entrenar el modelo con un conjunto de datos más pequeño y específico, para especializarlo en una tarea o comportamiento.


Por ejemplo: un modelo ajustado para atención al cliente, otro para generar código en Python, o uno especializado en medicina o derecho.


Durante el fine-tuning, el modelo aprende qué tipo de respuestas son deseables para su dominio.

Así, su conocimiento general se adapta a un propósito particular.

Una evolución del fine-tuning es el instruction tuning, que consiste en entrenar al modelo con ejemplos de pares instrucción → respuesta.


Ejemplo:

Instrucción: "Explicá la teoría de la evolución en pocas palabras."

Respuesta: "La teoría de la evolución describe cómo las especies cambian con el tiempo mediante la selección natural."


Después de ver miles de estos ejemplos, el modelo aprende que cuando alguien escribe algo como:

> “Contame brevemente cómo funciona X”

…debe responder de forma informativa, concisa y alineada con la intención del usuario.


Este es el paso que convierte un modelo base en algo más parecido a un asistente útil.


El Reinforcement Learning from Human Feedback (Aprendizaje por refuerzo a partir de retroalimentación humana) va un paso más allá.

En lugar de entrenar solo con ejemplos escritos, el modelo se ajusta usando la opinión de evaluadores humanos.


El proceso funciona así:

  1. El modelo genera varias posibles respuestas a una misma pregunta.
  2. Personas reales eligen cuál es la mejor, más útil o más segura.
  3. El sistema aprende a preferir las respuestas mejor valoradas.


De esta forma, el modelo no solo aprende lenguaje, sino también criterios de comportamiento: ser claro, respetuoso, evitar sesgos o rechazar solicitudes inapropiadas.


Incluso con todos estos ajustes, el modelo todavía depende de cómo lo usamos.

Ahí entra en juego el prompt engineering, o ingeniería de instrucciones: la práctica de formular entradas (prompts) de manera que el modelo produzca el resultado que buscamos.


Por ejemplo:

En lugar de: “Explicame Python.”

Mejor: “Explicame Python como si fuera mi primer lenguaje de programación.”

En lugar de: “Escribí un poema.”

Mejor: “Escribí un poema corto y humorístico sobre un programador que no duerme.”


Un buen prompt actúa como un mapa mental que guía al modelo hacia el tipo de respuesta deseada.

Y aunque los modelos actuales son más robustos, la forma de preguntar sigue siendo clave.


Gracias a estas técnicas, hoy existen:

  • Modelos generales (como GPT o Gemini),
  • Modelos especializados (como los de código, salud o educación),
  • y modelos adaptativos, que se ajustan dinámicamente según la conversación o el contexto.


El fine-tuning también puede hacerse de manera local o privada, permitiendo que empresas o instituciones adapten un modelo general a sus propios datos sin compartirlos públicamente.


Un modelo generativo no nace “inteligente”: aprende primero cómo hablar, luego cómo comportarse, y finalmente cómo adaptarse a cada situación.


El proceso completo es:

  1. Entrenamiento base: aprende el lenguaje.
  2. Fine-tuning / Instruction tuning: aprende tareas específicas y cómo responder.
  3. RLHF: se alinea con la forma en que las personas esperan que actúe.
  4. Prompt engineering: lo guiamos en tiempo real con buenas instrucciones.



miércoles, 3 de diciembre de 2025

for basado en rango vs std::for_each en C++


Desde C++11, C++ ofrece dos formas elegantes de recorrer contenedores:

el bucle for basado en rango y el algoritmo std::for_each de la STL (Standard Template Library).

Aunque ambos hacen lo mismo, hay diferencias importantes en estilo, expresividad y flexibilidad.


for basado en rango


La forma más simple y legible para recorrer elementos:


#include <iostream>

#include <vector>


int main() {

    std::vector<int> numeros = {1, 2, 3, 4, 5};


    for (int n : numeros)

        std::cout << n << " ";

}


Ventajas:

  • Sintaxis clara y directa.
  • Ideal para recorrer contenedores completos.
  • Soporta modificación con referencias (int& n).

Desventajas:

  • No se puede interrumpir fácilmente ni combinar con otros algoritmos STL.
  • No retorna nada.

std::for_each


std::for_each pertenece al encabezado <algorithm> y aplica una función o lambda a cada elemento del rango.


#include <iostream>

#include <vector>

#include <algorithm>


int main() {

    std::vector<int> numeros = {1, 2, 3, 4, 5};


    std::for_each(numeros.begin(), numeros.end(),

                  [](int n) { std::cout << n << " "; });

}


Ventajas:

  • Permite integrar lambdas o funciones personalizadas.
  • Compatible con pipelines de algoritmos STL.
  • Puede retornar un iterador al final del rango.

Desventajas:

  • Sintaxis más verbosa.
  • Menos legible para tareas simples.


Ambos pueden modificar los elementos si se usan referencias:


// Con for basado en rango

for (int& n : numeros)

    n *= 2;


// Con std::for_each

std::for_each(numeros.begin(), numeros.end(),

              [](int& n) { n *= 2; });


En Resumen: 

  • for basado en rango: simple, claro y moderno.
  • std::for_each: más flexible, ideal cuando querés combinarlo con otros algoritmos STL.


Ambos son válidos y conviven perfectamente en el C++ moderno.

lunes, 1 de diciembre de 2025

El bucle foreach en C++


En C++ no existe una palabra clave foreach como tal, pero desde C++11 se incorporó una sintaxis que cumple exactamente la misma función: el bucle for basado en rango (range-based for loop).


#include <iostream>

#include <vector>


int main() {

    std::vector<int> numeros = {1, 2, 3, 4, 5};


    for (int n : numeros) {

        std::cout << n << " ";

    }

}


Salida:

1 2 3 4 5


Este bucle recorre automáticamente todos los elementos del contenedor (como un std::vector, std::array, std::list, etc.) sin necesidad de usar índices ni iteradores manuales.

Si querés modificar los valores, podés usar una referencia:


for (int& n : numeros) {

    n *= 2;

}


Esto duplica cada elemento del vector.

Si no necesitás modificar los elementos, es buena práctica usar const:


for (const int& n : numeros) {

    std::cout << n << " ";

}

De esa forma, evitás copias innecesarias y el compilador puede optimizar mejor el código.

En Resumen: 

  • for (tipo var : contenedor) es la forma moderna y legible de recorrer colecciones en C++.
  • Usá referencias si querés modificar los elementos.
  • Usá const si solo vas a leerlos.
  • Funciona con cualquier contenedor que implemente begin() y end().


domingo, 30 de noviembre de 2025

Comparación de MongoDB y Couchbase para análisis en tiempo real

Couchbase me envio un benchmark comparando couchbase con mongodb para análisis en tiempo real y quería compartirlo: 

Modelos multimodales: cuando la IA entiende más que texto


Hasta ahora vimos cómo los Modelos de Lenguaje Grandes (LLM) son capaces de generar y comprender texto de forma sorprendente.

Pero el lenguaje no es la única forma en la que los humanos nos comunicamos: también usamos imágenes, sonidos, gestos y video.

Por eso, la nueva generación de inteligencia artificial apunta a algo más ambicioso: modelos capaces de entender y generar múltiples tipos de información al mismo tiempo.

A estos se los conoce como modelos multimodales.

El término multimodal viene de modalidad, que en este contexto significa tipo de dato o forma de comunicación.

Un modelo multimodal puede trabajar con más de una modalidad, por ejemplo: Texto, Imágenes, Audio, Video, etc

Así como un LLM aprende relaciones entre palabras, un modelo multimodal aprende relaciones entre palabras, píxeles y sonidos, entendiendo cómo se conectan entre sí.


Imaginá que escribís:

> “Mostrame un perro corriendo en la playa.”


Un modelo como DALL·E, Midjourney o Stable Diffusion convierte ese texto en una imagen realista que representa exactamente esa escena.


Lo inverso también es posible:

Subís una imagen y pedís:

> “Describí lo que ves.”


El modelo responde algo como:

> “Un perro marrón corriendo junto al mar en un día soleado.”


Eso significa que entendió el contenido visual y lo tradujo en texto coherente.

Los modelos multimodales se construyen extendiendo la arquitectura de los transformers.

Cada tipo de dato (texto, imagen, audio) se convierte en una representación numérica común, llamada embedding.

🔹 En el caso del texto, cada palabra se transforma en un vector.

🔹 En el caso de las imágenes, cada región o conjunto de píxeles también se transforma en vectores.

De esta forma, el modelo puede aprender correlaciones entre ambos mundos. Por ejemplo, que la palabra “perro” suele aparecer junto a formas con cuatro patas, hocico y pelaje.

El resultado es un sistema capaz de razonar sobre distintos tipos de información en simultáneo.

Lo interesante es que estos modelos no solo describen imágenes, sino que también razonan sobre ellas.

Por ejemplo, pueden interpretar gráficos, analizar documentos escaneados o incluso entender memes.

La IA generativa está dejando de ser “solo texto” para convertirse en una plataforma perceptiva.

Los modelos multimodales son un paso hacia sistemas que pueden:

  • mirar, escuchar y leer al mismo tiempo,
  • entender contextos complejos del mundo real,
  • y comunicarse de forma natural con las personas.


Por ejemplo, un asistente multimodal podría:

  • leer un documento PDF,
  • observar una foto de un diagrama,
  • escuchar una explicación verbal,
  • y responder con una síntesis combinando todo eso.


A medida que los modelos multimodales se integran con sensores, cámaras o dispositivos, nos acercamos a una IA más integrada con el entorno humano.

Esto abre posibilidades en:

  • Educación (IA que enseña con imágenes y audio)
  • Medicina (análisis de radiografías + texto clínico)
  • Programación (lectura de código y diagramas)
  • Arte (creación de video, música y texto combinados)


El objetivo final es una IA capaz de entender el mundo como nosotros: con todos los sentidos combinados.


Parámetros implícitos en Scala 3: given y using


Scala 3 introdujo una nueva forma de manejar los parámetros implícitos, reemplazando las viejas palabras clave implicit val y implicit def por un sistema más legible: given y using.


def saludar(nombre: String)(using saludo: String): Unit =

  println(s"$saludo, $nombre!")


given String = "Hola"


saludar("Emanuel") // Usa el valor dado automáticamente


given: define un valor que puede usarse de forma implícita.

using: marca el parámetro que puede ser resuelto automáticamente.


Ya no hay necesidad de usar la palabra implicit, lo que hace el código más claro y menos propenso a ambigüedades.

Veamos un ejemplo con varios contextos:


given idioma: String = "español"

given tono: String = "amistoso"


def saludar(nombre: String)(using idioma: String, tono: String): Unit =

  println(s"Saludo en $idioma con tono $tono: ¡Hola, $nombre!")


saludar("Emanuel")


El compilador resuelve ambos using buscando given del tipo adecuado en el ámbito actual.


Scala 2: implicit → flexible, pero a veces confuso.

Scala 3: given/using → más explícito y seguro.


El concepto sigue siendo el mismo: inyectar contextos automáticamente, pero con una sintaxis que favorece la claridad y la mantenibilidad del código.

martes, 25 de noviembre de 2025

Qué es un LLM (Large Language Model) ?



Hasta ahora vimos que un modelo generativo aprende a predecir la siguiente palabra dentro de una secuencia.

Pero ¿cómo pasamos de un simple predictor de texto a sistemas capaces de mantener conversaciones, razonar o escribir código?

La respuesta está en una sigla que probablemente ya viste muchas veces: LLM, o Large Language Model —Modelo de Lenguaje Grande.

Un LLM es un modelo de inteligencia artificial entrenado con cantidades masivas de texto —libros, artículos, código, conversaciones, sitios web— con el objetivo de aprender cómo funciona el lenguaje humano.

No se trata solo de palabras: el modelo aprende relaciones semánticas, contexto, estilo y tono.

Por eso, puede no solo completar frases, sino también adaptarse al contexto de una pregunta o instrucción.

Por ejemplo:

“Escribí un poema sobre gatos como si fueras Borges.”

El modelo comprende la estructura poética, el tema (gatos) y el estilo solicitado (Borges), y genera un texto coherente con todo eso.

El adjetivo Large (grande) no es casual.

Un LLM tiene miles de millones de parámetros, que son los “pesos” ajustados durante el entrenamiento.

Cuantos más parámetros, mayor capacidad tiene el modelo para reconocer patrones complejos y producir respuestas matizadas.

Esto significa que el modelo tiene un “cerebro” enorme, con miles de millones de conexiones que representan lo que aprendió sobre el lenguaje.

El salto que permitió construir los LLM modernos vino de una arquitectura publicada por Google en 2017: “Attention is All You Need”

En ese paper se presentó el Transformer, una estructura basada en un concepto revolucionario: la autoatención (self-attention).

Permite que el modelo “mire” todas las palabras del contexto al mismo tiempo, y decida a cuáles prestar más atención.

Por ejemplo, en la frase:

“El perro que mordió al cartero corrió hacia la casa.”

Para entender quién corrió, el modelo necesita conectar corrió con perro, no con cartero.

La atención le permite establecer esas relaciones de dependencia sin importar la distancia entre palabras.

Esa capacidad para manejar contexto global es lo que hace que los transformers sean tan potentes.


Cuando escribís una pregunta o prompt, el texto se convierte en tokens numéricos.

El modelo procesa esos tokens capa por capa, cada una aplicando atención y transformaciones matemáticas.

Al final, predice la probabilidad de cada posible palabra siguiente.


Por ejemplo:


Entrada: "La inteligencia artificial generativa es"

Salida probable: "una", "capaz", "un", "la"


El modelo elige la palabra más coherente según el contexto.

Luego vuelve a predecir la siguiente… y así sucesivamente, construyendo la respuesta palabra por palabra.


Los LLM no “piensan” como los humanos, pero su entrenamiento masivo les permite capturar regularidades del lenguaje y del pensamiento humano.

En la práctica, eso les da la capacidad de:

  • resumir textos,
  • traducir idiomas,
  • escribir código,
  • razonar sobre información textual,
  • y mantener conversaciones contextuales extensas.


Por eso, cuando hablamos con un modelo como ChatGPT, sentimos que hay comprensión real detrás —aunque lo que hay es una predicción probabilística extremadamente sofisticada.

Los LLM tienen límites: pueden inventar información (alucinaciones), carecen de comprensión profunda del mundo y dependen de los datos con los que fueron entrenados.

Sin embargo, su capacidad de generar texto coherente y útil en contextos muy variados los convierte en una de las herramientas más poderosas creadas hasta ahora.

domingo, 23 de noviembre de 2025

Cómo resuelve Scala los parámetros implícitos

En Scala, los parámetros implícitos permiten que una función reciba argumentos sin que el programador los pase explícitamente.

El compilador se encarga de buscar un valor adecuado en el ámbito para completar la llamada.

Veamos un ejemplo simple:


def saludar(nombre: String)(implicit saludo: String): Unit =

  println(s"$saludo, $nombre!")


implicit val saludoPorDefecto: String = "Hola"


saludar("Emanuel") // Usa el valor implícito definido arriba


Cuando Scala ve que falta un argumento para un parámetro implicit, sigue este proceso:

  1. Busca en el ámbito local: valores o funciones marcados como implicit del tipo requerido.
  2. Si no encuentra ninguno, busca en el objeto compañero (companion object) del tipo esperado.
  3. Si encuentra más de uno y no puede decidir cuál usar, el compilador lanza un error por ambigüedad.
  4. Si no encuentra ninguno, lanza un error por parámetro implícito no encontrado.

Veamos un ejemplo de ambigüedad:


implicit val saludo1: String = "Hola"

implicit val saludo2: String = "Buenas"


saludar("Emanuel") // Error: ambiguous implicits


El compilador no puede elegir entre `saludo1` y `saludo2`.


Scala resuelve los parámetros implícitos buscando un valor del tipo adecuado en:

El ámbito local,

Los imports activos,

Y los companion objects relacionados.


Es un mecanismo potente que permite propagar contextos automáticamente (por ejemplo, ExecutionContext, Ordering, Numeric, etc.), reduciendo la necesidad de pasar dependencias manualmente.

viernes, 21 de noviembre de 2025

Parámetros implícitos en Scala ¿y en otros languages?



Scala ofrece una poderosa característica llamada parámetros implícitos (implicit parameters), que permite pasar argumentos a funciones sin tener que especificarlos explícitamente cada vez. Esta capacidad se utiliza mucho para inyección de dependencias, contextos compartidos o type classes.


def saludar(nombre: String)(implicit saludo: String): Unit =

  println(s"$saludo, $nombre!")


implicit val saludoPorDefecto: String = "Hola"


saludar("Emanuel") // imprime "Hola, Emanuel!"


Aquí, el parámetro saludo se pasa de manera implícita gracias a la definición previa de un valor implicit.

Si se define otro valor implícito en el mismo alcance, ese será el utilizado, lo que permite una gran flexibilidad contextual.

Aunque el concepto de implícito es característico de Scala, existen ideas similares:

Haskell usa type classes, que se resuelven de forma implícita por el compilador.

Por ejemplo, la clase `Eq` o `Show` se comporta como una inyección automática de comportamientos según el tipo.


show 42 -- el compilador infiere automáticamente la instancia de Show Int


Rust usa traits y type inference, que cumplen un rol similar. Las implementaciones de traits se aplican automáticamente sin especificarlas cada vez.


println!("{}", 42); // Usa automáticamente el trait Display para i32


C# no tiene parámetros implícitos como tal, pero existen aproximaciones:

  • Inyección de dependencias en frameworks como ASP.NET.
  • Attributes y default parameters.
  • Desde C# 12, Primary constructors y default interface methods permiten inyectar comportamientos contextuales, aunque no son implícitos en tiempo de compilación.


Python tampoco tiene parámetros implícitos, pero se puede emular con:

  • Decoradores.
  • Context managers (with).
  • Argumentos por defecto o variables globales.


Los parámetros implícitos de Scala logran un equilibrio interesante entre claridad y potencia, especialmente en contextos funcionales.

Su uso debe ser cuidadoso, ya que abusar de ellos puede hacer que el flujo de datos sea menos evidente.

Sin embargo, cuando se aplican bien, son una herramienta que simplifica enormemente el código y reduce la verbosidad.

lunes, 17 de noviembre de 2025

¿Cómo funciona realmente #include en C++?


Cuando escribimos algo como:

#include <iostream>


podría parecer que el compilador “importa” la librería, pero en realidad el #include no es parte del lenguaje, sino una instrucción del preprocesador.

Lo que hace es copiar literalmente el contenido del archivo incluido dentro del código antes de compilar.


Por ejemplo, si tenés:

#include "miarchivo.h"


el preprocesador reemplaza esa línea por el texto completo de miarchivo.h.

Así, el compilador ve un solo archivo unificado.


¿Y qué pasa con las librerías estándar? Cuando hacés #include <iostream>, el compilador busca ese archivo en los directorios del sistema  (por ejemplo, /usr/include/c++/ en Linux).

Ese archivo sí existe y contiene declaraciones, no implementaciones.

Por ejemplo:


namespace std {

  extern ostream cout;

}


El archivo de cabecera sólo declara las funciones y objetos que vas a usar. Las implementaciones están en archivos binarios precompilados (.a, .lib, .so, .dll), que se vinculan en la etapa de linking.


El proceso completo es así:

  • Preprocesador: copia los headers (#include) y expande macros.
  • Compilación: convierte cada .cpp en un archivo objeto.
  • Linker: une esos objetos con las librerías que contienen las funciones reales.


Como los headers se copian literalmente, si se incluyen varias veces puede haber redefiniciones.

Por eso se usan las llamadas guardas de inclusión:


#ifndef MIARCHIVO_H

#define MIARCHIVO_H

// contenido

#endif


o la forma moderna:


#pragma once


En resumen, #include no importa código ejecutable:

  • solo inserta declaraciones que el compilador necesita conocer.
  • El código real vive en las librerías que se enlazan al final del proceso.


sábado, 15 de noviembre de 2025

Cómo funciona un modelo generativo


En el post anterior vimos qué es la Inteligencia Artificial Generativa y cómo puede crear contenido nuevo a partir de patrones aprendidos.

Ahora vamos a mirar debajo del capó: ¿cómo hace realmente un modelo para “inventar” texto, imágenes o música?

La respuesta puede resumirse en una idea:

> Un modelo generativo aprende a predecir lo que viene después.

Sí, suena simple. Pero detrás de esa predicción hay millones (o billones) de parámetros, una enorme cantidad de datos y un entrenamiento matemático fascinante.

Imaginemos que queremos que una máquina aprenda a escribir frases en español.

Para eso le damos millones de ejemplos: libros, artículos, correos, conversaciones.

El modelo analiza esas frases y aprende cómo se relacionan las palabras entre sí.


Por ejemplo, si ve muchas veces frases como:

> “El gato duerme en el sofá.”

> “El perro duerme en la cama.”


entonces entiende que después de “El gato” o “El perro” es muy probable que aparezca un verbo como duerme, corre o come.

Así, el modelo no memoriza frases completas, sino que aprende distribuciones de probabilidad:


> Dado un contexto (por ejemplo, “El gato”), ¿cuál es la palabra más probable que sigue?


Ese es el corazón de un modelo generativo.


Para que una máquina pueda trabajar con texto, primero debe convertir las palabras en números.

Cada fragmento de texto (una palabra, una sílaba o incluso una letra) se transforma en un token.


Por ejemplo:


“El gato duerme” → [101, 45, 202]


Estos números no tienen significado por sí mismos, pero el modelo los usa para representar el texto de forma matemática.

Con el tiempo, aprende que ciertos tokens aparecen juntos y en qué contextos.


Durante el entrenamiento, el modelo se enfrenta a miles de millones de ejemplos donde debe predecir la siguiente palabra.

Por ejemplo:


Entrada: "El gato"

Salida esperada: "duerme"


Cada vez que acierta, refuerza sus conexiones internas.

Cada vez que se equivoca, ajusta sus parámetros para acercarse un poco más a la respuesta correcta.

Ese proceso se repite millones de veces.


Con el tiempo, el modelo aprende cómo suena el lenguaje humano, y puede generar texto fluido simplemente repitiendo el proceso de predicción: elige una palabra, la agrega, vuelve a predecir la siguiente, y así sucesivamente.

Un modelo generativo moderno está formado por capas de neuronas artificiales conectadas entre sí.

Cada capa transforma la información, detecta patrones y pasa resultados a la siguiente.


Los modelos actuales, como los basados en la arquitectura Transformer, utilizan un mecanismo llamado atención (attention), que les permite decidir qué partes del texto son más relevantes para generar la siguiente palabra.


Por ejemplo, si el texto dice:

> “El gato que persiguió al perro estaba cansado.”


El modelo necesita “prestar atención” a *gato* (y no a *perro*) para entender que quien estaba cansado era el gato.

Eso es exactamente lo que hace el mecanismo de atención: ponderar el contexto de manera inteligente.


Supongamos que el modelo ya aprendió.

Ahora escribimos el inicio de una frase:

"El sol se"


El modelo analiza ese contexto y calcula probabilidades:


pone 0.8 a “pone”

0.1 a “oculta”

0.05 a “refleja”

0.05 a “enciende”


Puede elegir la más probable (pone), o una al azar según la distribución.

Luego repite el proceso con el nuevo contexto:


> “El sol se pone”


Y así, palabra por palabra, va construyendo texto coherente.

Lo mismo ocurre con píxeles en imágenes, notas en música o fotogramas en video.


Cuando vemos a ChatGPT escribir poesía o a DALL·E inventar ilustraciones, parece magia.

Pero en realidad, la creatividad de un modelo generativo proviene de su capacidad estadística para combinar patrones conocidos de forma nueva y coherente.


En cierto sentido, es una mezcla entre:

  • la memoria del lenguaje aprendido, y
  • la improvisación probabilística en cada predicción.


Comportamiento especial según el tipo genérico en C#


En C# no se puede sobrescribir un método solo porque el parámetro genérico es un tipo particular. El lenguaje no admite la especialización de clases genéricas como en C++, y la herencia no distingue entre MiClase<int> y MiClase<IMiInterfaz>.

Aun así, hay formas de lograr un comportamiento distinto según el tipo.

Una opción simple es decidir el comportamiento dentro del propio método:


class MiClase<T>

{

    public void Procesar(T valor)

    {

        if (valor is IMiInterfaz especial)

            ProcesarEspecial(especial);

        else

            ProcesarNormal(valor);

    }


    void ProcesarEspecial(IMiInterfaz x) => Console.WriteLine("Especial!");

    void ProcesarNormal(T x) => Console.WriteLine("Normal");

}


Si querés un despacho más flexible, podés usar dynamic:


class MiClase<T>

{

    public void Procesar(T valor)

        => ProcesarInterno((dynamic)valor);


    void ProcesarInterno(object v) => Console.WriteLine("Normal");

    void ProcesarInterno(IMiInterfaz v) => Console.WriteLine("Especial!");

}


También podés crear una subclase que se active solo para tipos que implementen una interfaz:


class MiClase<T>

{

    public virtual void Procesar(T x) => Console.WriteLine("Normal");

}


class MiClaseEspecial<T> : MiClase<T> where T : IMiInterfaz

{

    public override void Procesar(T x) => Console.WriteLine("Especial!");

}


En resumen, C# no permite sobrescribir métodos genéricos por tipo, pero sí es posible lograr comportamiento especializado combinando pattern matching, dynamic o herencia con restricciones.

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.