Me encanto esta comparativa por lo que quería compartirla, básicamente es un benchmark en diferentes lenguajes :
Dejo link
Me encanto esta comparativa por lo que quería compartirla, básicamente es un benchmark en diferentes lenguajes :
Dejo link
Cuando diseñamos tipos personalizados, muchas veces necesitamos que se comporten como si fueran valores comunes. Por ejemplo, si tenemos un struct Metros, queremos poder escribir:
Metros distancia = 100.0;
En lugar de tener que usar un constructor o un método auxiliar. O, si ya tenemos un objeto de tipo Metros, queremos poder convertirlo a double sin esfuerzo.
Sin conversiones definidas, tendríamos que hacer algo como:
var distancia = new Metros(100.0);
double d = distancia.Valor;
Definiendo operadores de conversión, podemos hacer que esta interacción sea más fluida.
El modificador implicit permite definir una conversión automática cuando no hay pérdida de información. El compilador permitirá esta conversión sin necesidad de cast.
Por ejemplo:
public struct Metros
{
public double Valor { get; }
public Metros(double valor) => Valor = valor;
public static implicit operator Metros(double valor) => new Metros(valor);
}
Uso:
Metros distancia = 100.0; // conversión implícita desde double
Cuando una conversión puede perder información, o puede fallar, se recomienda marcarla como explicit. Esto obliga al programador a escribir el cast, dejando claro que está realizando una operación que podría tener consecuencias.
Por ejemplo:
public static explicit operator double(Metros metros) => metros.Valor;
Uso:
double d = (double)distancia; // cast explícito requerido
Las conversiones implicit y explicit en C# permiten diseñar APIs más amigables, legibles y seguras. Usá implicit cuando estés seguro de que la conversión no va a fallar ni perder datos, y explicit cuando quieras que el programador sea consciente del riesgo de la conversión.
En POO, usamos clases abstractas o interfaces para definir estructuras que deben ser implementadas. En programación funcional, el enfoque es distinto, pero el objetivo es similar: ocultar detalles de implementación y exponer un comportamiento general.
Un ADT define un tipo por sus operaciones, no por cómo están implementadas. Por ejemplo:
data Pila a = Vacía | Empujar a (Pila a)
Este tipo Pila podría representar una pila genérica, y podríamos tener funciones que operen sobre ella sin importar cómo esté construida internamente.
En la programación funcional se identifican varios tipos de polimorfismo:
Polimorfismo Paramétrico: Permite escribir funciones genéricas sobre cualquier tipo. Es como los genéricos de Java, pero más poderoso:
identidad :: a -> a
identidad x = x
La función identidad funciona para cualquier tipo a.
Polimorfismo ad-hoc (Typeclasses / Traits / Protocolos) : En Haskell, Rust, Scala o Elixir podemos definir interfaces de comportamiento según el tipo. Esto recuerda al "método virtual" de POO.
class Metrico a where
distancia :: a -> a -> Double
instance Metrico (Double, Double) where
distancia (x1, y1) (x2, y2) =
sqrt ((x2 - x1)^2 + (y2 - y1)^2)
Veamos un ejemplo en Scala:
trait Metrico[T] {
def distancia(a: T, b: T): Double
}
implicit object Punto2D extends Metrico[(Double, Double)] {
def distancia(a: (Double, Double), b: (Double, Double)) =
math.sqrt(math.pow(a._1 - b._1, 2) + math.pow(a._2 - b._2, 2))
}
def calcularDistancia[T](a: T, b: T)(implicit m: Metrico[T]) =
m.distancia(a, b)
Pattern Matching como Polimorfismo Estructural: Otro recurso poderoso es el pattern matching, que permite seleccionar comportamiento según la "forma" del dato.
sealed trait Forma
case class Circulo(r: Double) extends Forma
case class Rectangulo(ancho: Double, alto: Double) extends Forma
def area(f: Forma): Double = f match {
case Circulo(r) => math.Pi * r * r
case Rectangulo(a, h) => a * h
}
¿Y qué ganamos con esto?
La programación funcional ofrece mecanismos muy sólidos y expresivos para manejar abstracción y polimorfismo. Aunque no se usa herencia, se logra el mismo efecto (o incluso uno más flexible) usando funciones genéricas, pattern matching y typeclasses.
Ninguna combinación de bibliotecas JS puede ofrecerte todas estas garantías. ¡Provienen del diseño del propio lenguaje! Y gracias a estas garantías, es bastante común que los programadores de Elm digan que nunca se sintieron tan seguros programando. Seguros para añadir funciones rápidamente. Seguros para refactorizar miles de líneas. ¡Pero sin la ansiedad de pensar que se te ha pasado algo importante!
En este sentido, el equipo del Archivo de Anna afirma proporcionar acceso a los metadatos de los materiales de Open Library, ser una copia de seguridad de las bibliotecas fantasma Library Genesis y Z-Library, presentar información sobre ISBN, no almacenar materiales protegidos por derechos de autor en su sitio web y solo indexar metadatos que ya están disponibles públicamente. Anna's Archive señala que su sitio web, un proyecto sin ánimo de lucro, acepta donaciones para cubrir gastos (alojamiento, nombres de dominio, desarrollo y relacionados).
Dejo link:
Pero hay algo que siempre nos hizo ruido:
Lo más raro es que una clase padre puede ser hija de otra clase. Rarisisimo...
Todo empieza con el inglés. En POO se habla de parent class para referirse a la clase de la cual heredan otras. Y parent, significa “padre o madre”.
Pero claro, en español, por alguna razón misteriosa que seguro involucra a la Real Academia, siempre traducimos parent class como “clase padre”, y no “clase madre” o “clase progenitor/a” (aunque esa última suena a trámites en ANSES).
Entonces… ¿por qué decimos “clase hija”? Acá la gramática mete la cuchara. Como la palabra “clase” es femenina, cuando hablamos de su descendencia lógica usamos “hija” para que concuerde: La clase padre tiene muchas clases hijas.
¿Y si dijéramos “clase madre”?
¡Podemos! No hay ninguna ley que lo impida. De hecho, si queremos romper esquemas y escribir:
class Mamífero // clase madre
class Perro extends Mamífero // clase hija
...nadie de Scala te va a venir a buscar. Al contrario, tal vez sumes puntos con tus profes de literatura.
Eso sí, el término "clase madre" no es tan común, así que si lo usás, preparate para explicar o educar. (O poner una nota al pie tipo “uso madre porque soy inclusivo/a y rebelde”).
La POO no distingue género, pero el lenguaje humano sí. Y en nuestra necesidad de ponerle nombre a todo, terminamos replicando convenciones culturales sin cuestionarlas.
¿Querés decir clase madre? ¡Decilo!
¿Preferís clase base? ¡También está bien!
Lo importante es que tus clases compilen… y que no traumen a sus hijas.
Es decir, queremos qu esta solución sea:
Muchos lenguajes modernos han ido incorporando construcciones para resolver este problema. Veamos cómo lo hacen C#, Go y Rust.
A partir de C# 7.2, se introdujo Span<T>, una estructura de tipo ref struct que representa una ventana sobre memoria contigua.
int[] datos = { 10, 20, 30, 40 };
Span<int> segmento = datos.AsSpan(1, 2); // contiene {20, 30}
Podés usar Span<T> para:
Span<byte> buffer = stackalloc byte[1024]; // sin heap
Limitaciones:
Go tiene slices desde siempre: son una capa por encima de los arrays. Un slice guarda un puntero al array subyacente, longitud y capacidad.
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // contiene {2, 3, 4}
Ventajas:
s[0] = 99 // también cambia arr[1]
La semántica de slicing en Go es natural y permite componer operaciones sin asignar memoria.
Rust maneja este problema con referencias segmentadas: &[T] para vistas inmutables y &mut [T] para mutables.
let arr = [1, 2, 3, 4];
let slice = &arr[1..3]; // &[2, 3]
Características:
fn print_slice(s: &[i32]) {
for val in s {
println!("{}", val);
}
}
¿Y cuál conviene?
Es decir, depende del lenguaje que estes usando ...
Y Otros lenguajes tambien tenemos cosas parecidas:
En la práctica, estas estructuras son fundamentales para escribir código eficiente, especialmente en procesamiento de datos, parsers, sistemas embebidos o cualquier aplicación donde el rendimiento importa.
Pero de igual manera nos podemos preguntar ¿No podría causar problemas si múltiples hilos usan el mismo objeto?
Veamos por qué no, y en qué casos sí.
Cuando marcamos una clase con @Component, @Service, @Repository, etc., sin indicar un @Scope, Spring crea una única instancia de esa clase cuando arranca el contexto, y luego la reutiliza para inyectarla donde haga falta.
@Service
public class MiServicio {
public void hacerAlgo() {
// lógica
}
}
Este bean se compartirá entre todas las partes de la aplicación que lo necesiten.
¿Y si lo usan varios hilos?
Acá es donde entra el concepto de thread safety. Si el bean no guarda estado mutable (es stateless), no hay ningún problema. Puede ser accedido por múltiples hilos al mismo tiempo sin consecuencias.
Pero si el bean mantiene estado mutable (por ejemplo, una variable de instancia que se modifica en cada método), entonces puede haber condiciones de carrera, errores y comportamiento impredecible.
Veamos un ejemplo peligroso:
@Component
public class ContadorCompartido {
private int contador = 0;
public void incrementar() {
contador++;
}
public int getContador() {
return contador;
}
}
Este bean no es thread-safe: si lo usan múltiples hilos al mismo tiempo, el contador puede tener resultados inesperados.
Otra pregunta puede ser ¿Spring crea múltiples instancias si hay mucha carga?
No. Este es un mito común. Spring no crea múltiples instancias de un bean singleton automáticamente bajo carga. La única forma en que podrías tener más de una instancia es:
Podemos concluir que :
En Spring, los beans singleton son una gran herramienta para reutilización de lógica y eficiencia de memoria. Pero es tu responsabilidad asegurarte de que no mantengan estado mutable que pueda generar conflictos en concurrencia.
Elm es un lenguaje compilado que genera JavaScript. Fue creado por Evan Czaplicki y se enfoca en facilitar la construcción de aplicaciones web escalables y mantenibles. Entre sus características más destacadas están:
¿Por qué usar Elm?
Elm organiza las aplicaciones con un patrón simple basado en tres conceptos:
Este patrón ha influido incluso en bibliotecas como Redux en JavaScript.
Veamos un poco de código:
module Main exposing (..)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
-- Modelo
type alias Model = Int
-- Mensajes
type Msg = Increment | Decrement
-- Estado inicial
init : Model
init = 0
-- Actualización del modelo
update : Msg -> Model -> Model
update msg model =
case msg of
Increment -> model + 1
Decrement -> model - 1
-- Vista
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]
-- Programa principal
main =
Browser.sandbox { init = init, update = update, view = view }
Este ejemplo muestra un contador que incrementa y decrementa con botones, ¡todo en unas pocas líneas claras!
Herramientas y Ecosistema:
Elm es una opción excelente para quienes quieren construir interfaces web seguras, claras y libres de errores. Su enfoque funcional y su arquitectura consistente lo convierten en un lenguaje ideal para proyectos de frontend con alta demanda de calidad.
Span<T> es una estructura stack-only que representa una ventana mutable sobre un bloque contiguo de memoria. Podés usarlo para acceder, cortar o modificar datos de arrays, slices de strings, buffers nativos, y más, sin necesidad de copiar datos.
Veamos un ejemplo:
int[] numbers = { 1, 2, 3, 4, 5 };
Span<int> slice = numbers.AsSpan(1, 3); // Contiene 2, 3, 4
slice[0] = 42;
Console.WriteLine(numbers[1]); // Muestra 42 (modificó el array original)
¿Por qué usar Span<T>?
Limitaciones:
Veamos un ejemplo de string:
ReadOnlySpan<char> span = "Hola Mundo".AsSpan(5);
Console.WriteLine(span.ToString()); // Mundo
Esto es ideal para parsear strings sin crear substrings intermedias.
Span<T> es una herramienta fundamental si querés escribir código de alto rendimiento en .NET. Es ideal para manipular datos binarios, strings o buffers, con el mínimo impacto en el garbage collector. Aunque tiene limitaciones (no se puede escapar del stack), su potencia compensa con creces en escenarios críticos de performance.
Veamos un ejemplo:
function incrementar(valor) {
valor.numero++;
}
let puntero = { numero: 10 };
incrementar(puntero);
console.log(puntero.numero); // 11
Aquí puntero simula un puntero: su campo numero puede ser modificado por funciones.
Se pasa la referencia al objeto, no una copia.
Otra forma es con arreglos para simular puntero a variables primitivas
function setValor(arr, nuevoValor) {
arr[0] = nuevoValor;
}
let x = [5];
setValor(x, 42);
console.log(x[0]); // 42
x es un arreglo de un solo elemento. Se comporta como una caja que puede modificarse dentro de funciones.
Esto puede verse como una simulación de un int* en C++.
Veamos un ejemplo sin utilizar funciones:
let a = { valor: 10 };
let b = a; // b apunta al mismo objeto que a
b.valor = 99;
console.log(a.valor); // 99
console.log(b.valor); // 99
a y b apuntan al mismo objeto.
Cambiar b.valor también afecta a.valor.
También se puede utilizar arreglos:
let x = [42];
let y = x;
y[0] = 100;
console.log(x[0]); // 100
console.log(y[0]); // 100
En JavaScript, los valores primitivos (números, strings, booleanos, etc.) se pasan por valor, pero los objetos y arrays se pasan por referencia y podemos utilizarlos para simular punteros.
Cuando está activado, el compilador trata los tipos de referencia como no anulables por defecto, a menos que explícitamente los marques con ?.
Veamos un ejemplo:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Y en el projecto:
string name = null; // Warning: posible asignación nula
string? nickname = null; // Permitido
con Nullable deshabilitado:
string name = null; // No hay advertencias, aunque puede fallar en runtime
Activar Nullable te permite:
Usar <Nullable>enable</Nullable> en tus proyectos de C# modernos es una excelente práctica. Aumenta la robustez del código y te da más control sobre los errores relacionados con null, una de las fuentes más comunes de fallos en producción.
La palabra clave unsafe en C# habilita el uso de punteros, acceso directo a memoria y operaciones que normalmente están fuera del alcance del entorno de ejecución administrado del .NET runtime. En otras palabras, permite usar características similares a C/C++, con sus pros y contras.
Para usarlo, debés marcar bloques, métodos o estructuras con la palabra clave unsafe. También tenés que habilitarlo en el proyecto.
En el `.csproj`:
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Veamos un ejemplo:
unsafe
{
int valor = 42;
int* puntero = &valor;
Console.WriteLine(*puntero); // Imprime 42
}
Cuando queremos apuntar a arrays o strings, que son gestionados por el Garbage Collector, necesitamos usar fixed para evitar que el objeto se mueva en memoria:
unsafe
{
int[] numeros = { 10, 20, 30 };
fixed (int* p = numeros)
{
Console.WriteLine(p[1]); // Imprime 20
}
}
Que podemos hacer con unsafe?
Pero ¿Cuándo usamos unsafe?
Como se pueden imaginar esto es tan util como peligroso, que es lo que puede salir mal?
Dentro de unsafe, podemos usar sizeof` para obtener el tamaño de tipos primitivos:
unsafe
{
Console.WriteLine(sizeof(int)); // 4
Console.WriteLine(sizeof(byte)); // 1
}
También podés usarlo con nint, nuint, float, double, etc.
Si queremos evitar unsafe pero aún así trabajar con memoria de forma eficiente:
El modo unsafe en C# te da acceso a un poder inmenso, pero con gran responsabilidad. Es una herramienta útil para situaciones específicas donde el rendimiento o la interoperabilidad lo justifican, pero debe usarse con precaución y conocimiento.
Cuando activás esta propiedad en el archivo .csproj, el compilador de C# agrega automáticamente un conjunto de using comunes a todos los archivos del proyecto, sin que tengas que escribirlos vos mismo.
Por ejemplo:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Gracias a esto, podés usar tipos como List<string>, Task, HttpClient, etc., directamente, sin declarar explícitamente:
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net.Http;
¿Qué usings incluye? Depende del tipo de proyecto (console, ASP.NET, etc.), pero algunos de los más comunes son:
Si querés ver la lista exacta, podés mirar el archivo generado: obj/YourProject.GlobalUsings.g.cs
<ImplicitUsings> es una pequeña gran herramienta que simplifica la escritura de código en C#, especialmente útil en proyectos nuevos. Al eliminar la necesidad de repetir los mismos using una y otra vez, te permite concentrarte en lo que realmente importa: la lógica de tu aplicación.
Esta característica permite dividir y extender la definición de estos elementos, haciendo que el código sea más limpio, modular y mantenible.
Una clase parcial es aquella que puede ser dividida en múltiples archivos.
Cada fragmento de la clase debe usar el modificador partial.
Veamos un ejemplo:
// Archivo Persona_Datos.cs
public partial class Persona
{
public string Nombre { get; set; }
}
// Archivo Persona_Operaciones.cs
public partial class Persona
{
public string ObtenerNombreCompleto() => Nombre;
}
Al compilar, el compilador junta todas las partes como si estuvieran en un único archivo.
Un método parcial (partial void) permite declarar un método que puede (o no) ser implementado en otra parte de la clase.
Si no se implementa, el compilador simplemente lo ignora, como si nunca hubiera existido.
Vamos con el ejemplo:
// Declaración
public partial class Persona
{
partial void ValidarNombre();
}
// Implementación opcional
public partial class Persona
{
partial void ValidarNombre()
{
if (string.IsNullOrEmpty(Nombre))
{
Console.WriteLine("Nombre inválido");
}
}
}
Propiedades Parciales (desde C# 12)
Desde C# 12, se pueden crear propiedades parciales, permitiendo que la lógica de getters y setters sea definida en distintas partes del código.
public partial class Persona
{
public partial string Nombre { get; set; }
}
// En otro archivo:
public partial class Persona
{
public partial string Nombre
{
get => _nombre;
set => _nombre = value.Trim();
}
private string _nombre;
}
Con esta característica se puede separar la definición de una propiedad de su comportamiento, facilitando la generación automática de propiedades o su personalización.
Un indexador parcial permite dividir la definición de un indexador (this[...]) en varias partes.
Por ejemplo:
public partial class MiColeccion
{
public partial string this[int index] { get; set; }
}
// En otro archivo:
public partial class MiColeccion
{
private string[] _datos = new string[10];
public partial string this[int index]
{
get => _datos[index];
set => _datos[index] = value;
}
}
Partial es muy importante porque:
El soporte de C# para clases, métodos, propiedades e indexadores parciales te da un gran poder para modularizar tu código de forma clara y ordenada.
Esta es una característica que, bien aprovechada, puede hacer una gran diferencia en proyectos de cualquier tamaño.