Programación genérica en TypeScript
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/
Al igual que en Kotlin, TypeScript también soporta el polimorfismo paramétrico utilizando tipos genéricos. Esto permite definir funciones, clases e interfaces que pueden trabajar con cualquier tipo de dato sin especificarlo de antemano.
Función Genérica en TypeScript
En TypeScript, la función de identidad se implementa con tipos genéricos de la siguiente manera:
- Código esencial
- Código completo
test('when called with a string, then returns the string', () => {
const value = 'hello';
expect(identity(value)).toBe(value);
});
test('when called with a number, then returns the number', () => {
const value = 42;
expect(identity(value)).toBe(value);
});
import { describe, expect, test } from '@jest/globals';
import { identity } from '../../src/generics/identity';
describe('given an identity function', () => {
test('when called with a string, then returns the string', () => {
const value = 'hello';
expect(identity(value)).toBe(value);
});
test('when called with a number, then returns the number', () => {
const value = 42;
expect(identity(value)).toBe(value);
});
test('when called with an object, then returns the object', () => {
const value = { foo: 'bar' };
expect(identity(value)).toBe(value);
});
});
export function identity<T>(value: T): T {
return value;
}
Clases Gen éricas en TypeScript
TypeScript también soporta clases genéricas. Un ejemplo típico es la clase Box
que puede almacenar un valor de cualquier tipo T
:
- Código esencial
- Código completo
test('when containing a string, then returns the string', () => {
const box: Box<string> = { value: 'hello' };
expect(box.value).toBe('hello');
});
test('when containing a number, then returns the number', () => {
const box: Box<number> = { value: 42 };
expect(box.value).toBe(42);
});
import { describe, expect, test } from '@jest/globals';
import { Box } from '../../src/generics/box';
describe('given a box', () => {
test('when containing a string, then returns the string', () => {
const box: Box<string> = { value: 'hello' };
expect(box.value).toBe('hello');
});
test('when containing a number, then returns the number', () => {
const box: Box<number> = { value: 42 };
expect(box.value).toBe(42);
});
test('when containing an object, then returns the object', () => {
const box: Box<{ foo: string }> = { value: { foo: 'bar' } };
expect(box.value).toEqual({ foo: 'bar' });
});
})
export class Box<T> {
constructor(public value: T) {}
}
Interfaces Genéricas en TypeScript
Similar a Kotlin, TypeScript también permite definir interfaces genéricas. Aquí está un ejemplo de una interfaz Repository
genérica para cualquier tipo T
:
- Código esencial
- Código completo
test('when saving a user, then can find the user by id', () => {
const repository = new UserRepository();
const user: User = { id: 1, name: 'Jeon Geuk-jin' };
expect(repository.findById(user.id)).toBeUndefined();
repository.save(user);
expect(repository.findById(user.id)).toEqual(user);
});
export interface Repository<T> {
save(value: T): void;
findById(id: number): T | undefined;
}
export class UserRepository implements Repository<User> {}
import { describe, expect, test } from '@jest/globals';
import { User } from '../../../src/generics/repo/user';
import { UserRepository } from '../../../src/generics/repo/repository';
describe('given a user repository', () => {
test('when saving a user, then can find the user by id', () => {
const repository = new UserRepository();
const user: User = { id: 1, name: 'Jeon Geuk-jin' };
expect(repository.findById(user.id)).toBeUndefined();
repository.save(user);
expect(repository.findById(user.id)).toEqual(user);
})
})
export interface User {
id: number;
name: string;
}
import { User } from './user.ts';
export interface Repository<T> {
save(value: T): void;
findById(id: number): T | undefined;
}
export class UserRepository implements Repository<User> {
private users: User[] = [];
save(user: User): void {
this.users.push(user);
}
findById(id: number): User | undefined {
return this.users.find(user => user.id === id);
}
}
Comparación Final
Característica | Kotlin | TypeScript |
---|---|---|
Función Genérica | fun <T> identity(value: T): T | function identity<T>(value: T): T |
Clases Genéricas | class Box<T>(val value: T) | class Box<T> { constructor(public value: T) } |
Interfaces Genéricas | interface Repository<T> { ... } | interface Repository<T> { ... } |
Tipo de Parámetro Inferido | Inferencia fuerte | Inferencia flexible |
Sistema de Tipos | Estático y seguro en tiempo de compilación | Estático, pero más flexible |
Beneficios y limitaciones
Beneficios
- Flexibilidad: TypeScript permite mayor flexibilidad en la inferencia de tipos, lo que puede hacer que sea más fácil de usar en proyectos donde el tipado rígido no es siempre necesario.
- Integración con JavaScript: Al ser un superconjunto de JavaScript, TypeScript facilita la adopción gradual de tipos en proyectos JavaScript existentes, lo que lo convierte en una excelente opción para proyectos que evolucionan.
- Desarrollo web: TypeScript es ampliamente utilizado en el desarrollo de aplicaciones web modernas, particularmente en frameworks como Angular, lo que lo hace relevante para desarrolladores web.
Limitaciones
- Tipado menos estricto: Aunque TypeScript tiene un sistema de tipos estáticos, es más flexible que el de Kotlin, lo que puede llevar a errores difíciles de detectar en aplicaciones grandes si no se aplica correctamente.
- Menor seguridad de tipos: Debido a que TypeScript permite el tipado opcional y es más permisivo, no siempre se garantiza la misma seguridad en tiempo de compilación que en Kotlin.
- Complejidad añadida: En proyectos grandes, la configuración y gestión de tipos en TypeScript puede volverse compleja, especialmente cuando se integran librerías de JavaScript que no tienen tipos definidos.
¿Qué Aprendimos?
En esta lección, hemos explorado cómo TypeScript soporta el polimorfismo paramétrico de manera similar a Kotlin a través del uso de tipos genéricos. Ambos lenguajes permiten escribir código más flexible y reutilizable al permitir que funciones, clases e interfaces trabajen con cualquier tipo de dato. Sin embargo, también hemos visto algunas diferencias clave en sus enfoques, como la flexibilidad del sistema de tipos de TypeScript frente a la mayor rigidez y seguridad del sistema de tipos de Kotlin.
Puntos clave
- TypeScript ofrece un sistema de tipos más flexible, adecuado para proyectos que requieren integrarse con código JavaScript existente o aplicaciones web modernas.
- Kotlin, por otro lado, proporciona un sistema de tipos más estricto y seguro, lo que garantiza una mayor seguridad en tiempo de compilación, evitando errores de tipo comunes.
- Ambos lenguajes permiten la reutilización y generalización de código a través de funciones genéricas, clases genéricas, e interfaces genéricas, promoviendo buenas prácticas de programación como la abstracción y la seguridad de tipos.
Con esto, puedes aplicar el polimorfismo paramétrico en ambos lenguajes para escribir código más flexible y robusto, adaptado a diferentes tipos de datos sin comprometer la seguridad del código.
Bibliografías Recomendadas
- 📚 "Generics". (2023). Baumgartner, S., en Typescript Cookbook: Real world type-level programming, (pp. 109–142.) O'Reilly.