Welcome back to the most advanced genetics laboratory in Jurassic Park! Dr. Henry Wu today invites us to explore one of the most powerful TypeScript mechanisms - generic functions. Just as advanced genetic engineering allows us to create new dinosaur species with different DNA, generic functions allow the creation of flexible, reusable solutions that work with different data types while maintaining full type safety.
Generic functions are functions that can work with different data types while maintaining full type control. Instead of defining a specific type for a parameter and return value, we use type parameters (usually denoted as
T, U, K, etc.), which are "filled in" with specific types at the moment the function is called.Imagine that instead of creating a separate function for each dinosaur species, you can create one generic function that will work with every species!
Let's look at how generic functions are defined and used in TypeScript:
1// Simple generic function - returns the same element that was passed
2function geneticIdentity<T>(obiekt: T): T {
3 return obiekt;
4}
5
6// Use with different types
7const dnaChain: string = geneticIdentity<string>("ACGTACGT");
8const wiekTriceratopsa: number = geneticIdentity<number>(12);
9const aktywnyStatus: boolean = geneticIdentity<boolean>(true);
10
11// TypeScript can often infer the generic type from the passed argument
12const poziomAgresji = geneticIdentity(7); // TypeScript infers T as numberIn the above example:
geneticIdentity with type parameter TT and returns a value of the same typeGeneric functions can have multiple type parameters, allowing more complex operations:
1// Generic function with two type parameters
2function parujDaneDinosaurs<T, U>(pierwsze: T, drugie: U): [T, U] {
3 return [pierwsze, drugie];
4}
5
6// Sample usage
7interface DanePodstawoweDinosaura {
8 nazwa: string;
9 species: string;
10 wiek: number;
11}
12
13interface DaneZdrowotne {
14 bodyTemperature: number;
15 heartRate: number;
16 poziomEnergii: number;
17}
18
19const rexPodstawowe: DanePodstawoweDinosaura = {
20 nazwa: "Rex",
21 species: "Tyrannosaurus",
22 wiek: 7
23};
24
25const rexZdrowie: DaneZdrowotne = {
26 bodyTemperature: 38.2,
27 heartRate: 76,
28 poziomEnergii: 8.5
29};
30
31// Combining different types of data
32const fullRexData = parujDaneDinosaurs<DanePodstawoweDinosaura, DaneZdrowotne>(
33 rexPodstawowe,
34 rexZdrowie
35);
36
37console.log(`${fullRexData[0].nazwa} has body temperature ${fullRexData[1].bodyTemperature}°C`);Sometimes we want our generic functions to work only with specific data types that satisfy certain requirements. We can achieve this using generic constraints via the
extends keyword:1// Defining an interface describing minimum requirements
2interface PodstawoweInformacjeDinosaura {
3 id: string;
4 nazwa: string;
5 species: string;
6}
7
8// Generic function with constraint - T must contain at least the properties from PodstawoweInformacjeDinosaura
9function displayDinosaurInfo<T extends PodstawoweInformacjeDinosaura>(dino: T): void {
10 console.log(`ID: ${dino.id}`);
11 console.log(`Name: ${dino.nazwa}`);
12 console.log(`Species: ${dino.species}`);
13
14 // We can also access additional properties that may be present in T
15 console.log("Additional information:");
16 const podstawoweKlucze = Object.keys({} as PodstawoweInformacjeDinosaura);
17 Object.entries(dino).forEach(([klucz, value]) => {
18 if (!podstawoweKlucze.includes(klucz)) {
19 console.log(`- ${klucz}: ${value}`);
20 }
21 });
22}
23
24// Example usage with different types satisfying the constraint
25interface TRex extends PodstawoweInformacjeDinosaura {
26 biteForce: number;
27 runSpeed: number;
28}
29
30const rex: TRex = {
31 id: "TREX-001",
32 nazwa: "Rexy",
33 species: "Tyrannosaurus Rex",
34 biteForce: 12800,
35 runSpeed: 32
36};
37
38interface Velociraptor extends PodstawoweInformacjeDinosaura {
39 inteligencja: number;
40 polowanieWGrupie: boolean;
41}
42
43const raptor: Velociraptor = {
44 id: "VELOC-001",
45 nazwa: "Blue",
46 species: "Velociraptor",
47 inteligencja: 9,
48 polowanieWGrupie: true
49};
50
51// Both calls will work because both types extend PodstawoweInformacjeDinosaura
52displayDinosaurInfo(rex);
53displayDinosaurInfo(raptor);
54
55// This would not work because required properties are missing
56// displayDinosaurInfo({ waga: 5000, wiek: 12 });In the above example:
PodstawoweInformacjeDinosaura interface with minimum requirementsThe
keyof keyword allows constraining a generic parameter to the keys of another type. This is extremely useful when creating functions that operate on object keys:1// Function retrieving a property value based on its key
2function getDinosaurProperty<T, K extends keyof T>(obiekt: T, klucz: K): T[K] {
3 return obiekt[klucz];
4}
5
6// Sample dinosaur data
7interface DaneDinosaura {
8 id: string;
9 nazwa: string;
10 species: string;
11 wiek: number;
12 waga: number;
13 predator: boolean;
14 cechy: string[];
15}
16
17const triceratops: DaneDinosaura = {
18 id: "TRIC-001",
19 nazwa: "Tricky",
20 species: "Triceratops",
21 wiek: 15,
22 waga: 9000,
23 predator: false,
24 cechy: ["horns", "head shield", "strong legs"]
25};
26
27// Using the function with different keys
28const nazwaTriceratopsa = getDinosaurProperty(triceratops, "nazwa");
29console.log(`Name: ${nazwaTriceratopsa}`); // TypeScript knows this is a string
30
31const wiekTriceratopsa = getDinosaurProperty(triceratops, "wiek");
32console.log(`Age: ${wiekTriceratopsa} years`); // TypeScript knows this is a number
33
34const cechy = getDinosaurProperty(triceratops, "cechy");
35console.log(`Traits: ${cechy.join(", ")}`); // TypeScript knows this is string[]
36
37// This would not work because "nonExistentProperty" is not a key of DaneDinosaura
38// getDinosaurProperty(triceratops, "nonExistentProperty");Just as functions can have default values for their parameters, generic functions can have default types for type parameters:
1// Generic function with default type parameter
2function utworzRaportDinosaura<T = DaneDinosaura>(dane: T, etykieta: string = "Standard report"): { dane: T, etykieta: string, timestamp: Date } {
3 return {
4 dane,
5 etykieta,
6 timestamp: new Date()
7 };
8}
9
10// Using with default type
11const raportStandardowy = utworzRaportDinosaura(triceratops);
12console.log(`Report: ${raportStandardowy.etykieta}`);
13console.log(`Data for: ${raportStandardowy.dane.nazwa}`);
14
15// Using with a different type
16interface DaneKlimatyczne {
17 temperatura: number;
18 humidity: number;
19 pressure: number;
20 lokalizacja: string;
21}
22
23const daneKlimatyczneWybiegu: DaneKlimatyczne = {
24 temperatura: 28.5,
25 humidity: 72,
26 pressure: 1013,
27 lokalizacja: "Triceratops Enclosure"
28};
29
30const raportKlimatyczny = utworzRaportDinosaura<DaneKlimatyczne>(
31 daneKlimatyczneWybiegu,
32 "Climate report"
33);
34
35console.log(`Report: ${raportKlimatyczny.etykieta}`);
36console.log(`Temperature: ${raportKlimatyczny.dane.temperatura}°C`);Generic functions are especially useful when working with arrays and data collections:
1// Generic function for filtering an array
2function filterCollection<T>(kolekcja: T[], predykat: (element: T) => boolean): T[] {
3 return kolekcja.filter(predykat);
4}
5
6// Sample dinosaur data
7interface DinosaurBriefInfo {
8 id: string;
9 nazwa: string;
10 species: string;
11 predator: boolean;
12 waga: number;
13}
14
15const dinosaury: DinosaurBriefInfo[] = [
16 { id: "TREX-001", nazwa: "Rex", species: "Tyrannosaurus", predator: true, waga: 8000 },
17 { id: "VEL-001", nazwa: "Blue", species: "Velociraptor", predator: true, waga: 150 },
18 { id: "TRIC-001", nazwa: "Tricky", species: "Triceratops", predator: false, waga: 9000 },
19 { id: "STEG-001", nazwa: "Spike", species: "Stegosaurus", predator: false, waga: 5000 },
20 { id: "BRACH-001", nazwa: "Brachie", species: "Brachiosaurus", predator: false, waga: 40000 }
21];
22
23// Filtering predatory dinosaurs
24const predators = filterCollection<DinosaurBriefInfo>(
25 dinosaury,
26 dino => dino.predator
27);
28
29console.log("Predators in the park:");
30predators.forEach(dino => console.log(`- ${dino.nazwa} (${dino.species})`));
31
32// Filtering dinosaurs heavier than 10000 kg
33const heavyDinosaurs = filterCollection<DinosaurBriefInfo>(
34 dinosaury,
35 dino => dino.waga > 10000
36);
37
38console.log("\nHeavy dinosaurs:");
39heavyDinosaurs.forEach(dino => console.log(`- ${dino.nazwa}: ${dino.waga} kg`));
40
41// More advanced generic function for mapping an array
42function transformCollection<T, U>(kolekcja: T[], transformacja: (element: T) => U): U[] {
43 return kolekcja.map(transformacja);
44}
45
46// Creating simpler objects from dinosaurs
47const etykietyDinosaurs = transformCollection<DinosaurBriefInfo, { nazwa: string, typ: string }>(
48 dinosaury,
49 dino => ({
50 nazwa: dino.nazwa,
51 typ: dino.predator ? "predator" : "herbivore"
52 })
53);
54
55console.log("\nDinosaur labels:");
56etykietyDinosaurs.forEach(etykieta => console.log(`- ${etykieta.nazwa}: ${etykieta.typ}`));Not only functions can be generic - classes and their methods can also use this powerful feature:
1// Generic Kolekcja class - can store data of any type
2class Kolekcja<T> {
3 private elementy: T[] = [];
4
5 // Method adding an element to the collection
6 dodaj(element: T): void {
7 this.elementy.push(element);
8 }
9
10 // Method retrieving an element at a given index
11 pobierz(indeks: number): T | undefined {
12 if (indeks >= 0 && indeks < this.elementy.length) {
13 return this.elementy[indeks];
14 }
15 return undefined;
16 }
17
18 // Method returning all elements
19 wszystkie(): T[] {
20 return [...this.elementy];
21 }
22
23 // Method filtering elements
24 filtruj(predykat: (element: T) => boolean): T[] {
25 return this.elementy.filter(predykat);
26 }
27
28 // Generic method mapping to another type
29 mapuj<U>(transformacja: (element: T) => U): U[] {
30 return this.elementy.map(transformacja);
31 }
32
33 // Method returning the number of elements
34 itemsCount(): number {
35 return this.elementy.length;
36 }
37}
38
39// Example usage with dinosaurs
40const kolekcjaDinosaurs = new Kolekcja<DinosaurBriefInfo>();
41
42// Adding dinosaurs to the collection
43dinosaury.forEach(dino => kolekcjaDinosaurs.dodaj(dino));
44
45// Getting a specific dinosaur
46const pierwszy = kolekcjaDinosaurs.pobierz(0);
47if (pierwszy) {
48 console.log(`First dinosaur: ${pierwszy.nazwa} (${pierwszy.species})`);
49}
50
51// Filtering the collection
52const smallDinosaurs = kolekcjaDinosaurs.filtruj(dino => dino.waga < 1000);
53console.log(`Number of small dinosaurs: ${smallDinosaurs.length}`);
54
55// Mapping to health reports
56interface RaportZdrowia {
57 id: string;
58 nazwa: string;
59 statusZdrowia: string;
60 uwagi: string;
61}
62
63const raportyZdrowia = kolekcjaDinosaurs.mapuj<RaportZdrowia>(dino => ({
64 id: dino.id,
65 nazwa: dino.nazwa,
66 statusZdrowia: "good",
67 uwagi: dino.predator
68 ? "Exercise special caution"
69 : "Standard procedures"
70}));
71
72console.log("\nHealth reports:");
73raportyZdrowia.forEach(raport => {
74 console.log(`- ${raport.nazwa}: ${raport.statusZdrowia} (${raport.uwagi})`);
75});Just like functions and classes, interfaces and types can also be generic:
1// Generic interface for a storage system
2interface System<T> {
3 dodaj(element: T): void;
4 pobierz(id: string): T | null;
5 aktualizuj(id: string, element: T): boolean;
6 remove(id: string): boolean;
7 wszystkie(): T[];
8}
9
10// Implementation of a generic storage system for dinosaurs
11class BazaDanychDinosaurs<T extends { id: string }> implements System<T> {
12 private dane: Map<string, T> = new Map();
13
14 dodaj(element: T): void {
15 this.dane.set(element.id, element);
16 }
17
18 pobierz(id: string): T | null {
19 return this.dane.has(id) ? this.dane.get(id)! : null;
20 }
21
22 aktualizuj(id: string, element: T): boolean {
23 if (this.dane.has(id)) {
24 this.dane.set(id, element);
25 return true;
26 }
27 return false;
28 }
29
30 remove(id: string): boolean {
31 return this.dane.delete(id);
32 }
33
34 wszystkie(): T[] {
35 return Array.from(this.dane.values());
36 }
37}
38
39// Defining types using generic type
40type DaneZPrzypisanym<T> = {
41 dane: T;
42 przypisanyPracownik: string;
43 ostatniaAktualizacja: Date;
44};
45
46// Using the generic type
47type DinosaurZOpiekunem = DaneZPrzypisanym<DinosaurBriefInfo>;
48
49// Creating a database for dinosaurs with assigned keepers
50const bazaDanychZOpiekunami = new BazaDanychDinosaurs<DinosaurZOpiekunem>();
51
52// Adding records
53bazaDanychZOpiekunami.dodaj({
54 dane: dinosaury[0],
55 przypisanyPracownik: "Owen Grady",
56 ostatniaAktualizacja: new Date(),
57 id: dinosaury[0].id
58});
59
60bazaDanychZOpiekunami.dodaj({
61 dane: dinosaury[1],
62 przypisanyPracownik: "Dr. Henry Wu",
63 ostatniaAktualizacja: new Date(),
64 id: dinosaury[1].id
65});
66
67// Retrieving data
68const rexZOpiekunem = bazaDanychZOpiekunami.pobierz("TREX-001");
69if (rexZOpiekunem) {
70 console.log(`\nDinosaur ${rexZOpiekunem.dane.nazwa} is under the care of ${rexZOpiekunem.przypisanyPracownik}`);
71 console.log(`Last updated: ${rexZOpiekunem.ostatniaAktualizacja.toLocaleDateString()}`);
72}Let's look at a comprehensive example of how generic functions can be used in the Jurassic Park management system:
1// ===== JURASSIC PARK MANAGEMENT SYSTEM =====
2
3// Base interfaces
4interface ElementParku {
5 id: string;
6 nazwa: string;
7 typElementu: string;
8}
9
10interface ObszarParku extends ElementParku {
11 typElementu: "area";
12 coordinates: [number, number];
13 powierzchnia: number; // in hectares
14 securityLevel: 1 | 2 | 3 | 4 | 5;
15}
16
17interface WybiegDinosaurs extends ObszarParku {
18 typElementu: "enclosure";
19 residents: string[]; // Dinosaur IDs
20 environmentType: "forest" | "savanna" | "swamp" | "coast" | "water";
21 statusOgrodzenia: "active" | "inactive" | "under repair";
22}
23
24interface DinosaurParku extends ElementParku {
25 typElementu: "dinosaur";
26 species: string;
27 wiek: number;
28 waga: number;
29 predator: boolean;
30 lokalizacja: string; // Area ID
31 statusZdrowia: "excellent" | "good" | "needs attention" | "sick" | "critical";
32}
33
34interface PracownikParku extends ElementParku {
35 typElementu: "employee";
36 stanowisko: string;
37 accessLevel: 1 | 2 | 3 | 4 | 5;
38 specjalizacja: string[];
39}
40
41// Generic repository class
42class Repozytorium<T extends ElementParku> {
43 private elementy: Map<string, T> = new Map();
44
45 dodaj(element: T): void {
46 this.elementy.set(element.id, element);
47 }
48
49 pobierz(id: string): T | undefined {
50 return this.elementy.get(id);
51 }
52
53 wszystkie(): T[] {
54 return Array.from(this.elementy.values());
55 }
56
57 aktualizuj(id: string, dane: Partial<T>): boolean {
58 if (this.elementy.has(id)) {
59 const element = this.elementy.get(id)!;
60 this.elementy.set(id, { ...element, ...dane });
61 return true;
62 }
63 return false;
64 }
65
66 remove(id: string): boolean {
67 return this.elementy.delete(id);
68 }
69
70 filtruj(predykat: (element: T) => boolean): T[] {
71 return this.wszystkie().filter(predykat);
72 }
73}
74
75// Generic function for finding relationships between elements
76function findAssociations<T extends ElementParku, U extends ElementParku>(
77 sources: T[],
78 cele: U[],
79 predykat: (source: T, cel: U) => boolean
80): Array<[T, U[]]> {
81 return sources.map(source => {
82 const relatedTargets = cele.filter(cel => predykat(source, cel));
83 return [source, relatedTargets] as [T, U[]];
84 });
85}
86
87// Generic function for generating reports
88function generujRaport<T extends ElementParku>(
89 nazwa: string,
90 elementy: T[],
91 formatujElement: (element: T) => string
92): string {
93 let raport = `=== ${nazwa} ===\n`;
94 raport += `Generated on: ${new Date().toLocaleString()}\n`;
95 raport += `Number of elements: ${elementy.length}\n\n`;
96
97 if (elementy.length === 0) {
98 raport += "No data to display.\n";
99 } else {
100 elementy.forEach(element => {
101 raport += formatujElement(element) + "\n";
102 });
103 }
104
105 return raport;
106}
107
108// Simulating park data
109// Creating repositories
110const repoDinosaurs = new Repozytorium<DinosaurParku>();
111const areasRepo = new Repozytorium<ObszarParku>();
112const enclosuresRepo = new Repozytorium<WybiegDinosaurs>();
113const employeesRepo = new Repozytorium<PracownikParku>();
114
115// Adding data to repositories
116// Areas
117const obszarCentralny: ObszarParku = {
118 id: "OBS-001",
119 nazwa: "Central Area",
120 typElementu: "area",
121 coordinates: [20.123, -75.456],
122 powierzchnia: 25,
123 securityLevel: 1
124};
125areasRepo.dodaj(obszarCentralny);
126
127// Enclosures
128const wybiegTRex: WybiegDinosaurs = {
129 id: "WYB-001",
130 nazwa: "T-Rex Enclosure",
131 typElementu: "enclosure",
132 coordinates: [20.145, -75.460],
133 powierzchnia: 8,
134 securityLevel: 5,
135 residents: ["DINO-001"],
136 environmentType: "forest",
137 statusOgrodzenia: "active"
138};
139enclosuresRepo.dodaj(wybiegTRex);
140
141const wybiegRaptory: WybiegDinosaurs = {
142 id: "WYB-002",
143 nazwa: "Raptor Enclosure",
144 typElementu: "enclosure",
145 coordinates: [20.150, -75.465],
146 powierzchnia: 5,
147 securityLevel: 5,
148 residents: ["DINO-002", "DINO-003", "DINO-004", "DINO-005"],
149 environmentType: "savanna",
150 statusOgrodzenia: "active"
151};
152enclosuresRepo.dodaj(wybiegRaptory);
153
154// Dinosaurs
155const trex: DinosaurParku = {
156 id: "DINO-001",
157 nazwa: "Rexy",
158 typElementu: "dinosaur",
159 species: "Tyrannosaurus Rex",
160 wiek: 7,
161 waga: 8000,
162 predator: true,
163 lokalizacja: "WYB-001",
164 statusZdrowia: "good"
165};
166repoDinosaurs.dodaj(trex);
167
168const raptor1: DinosaurParku = {
169 id: "DINO-002",
170 nazwa: "Blue",
171 typElementu: "dinosaur",
172 species: "Velociraptor",
173 wiek: 4,
174 waga: 150,
175 predator: true,
176 lokalizacja: "WYB-002",
177 statusZdrowia: "excellent"
178};
179repoDinosaurs.dodaj(raptor1);
180
181// Employees
182const trener: PracownikParku = {
183 id: "PRAC-001",
184 nazwa: "Owen Grady",
185 typElementu: "employee",
186 stanowisko: "Raptor Trainer",
187 accessLevel: 4,
188 specjalizacja: ["Behaviorism", "Dinosaur training", "Field operations"]
189};
190employeesRepo.dodaj(trener);
191
192const naukowiec: PracownikParku = {
193 id: "PRAC-002",
194 nazwa: "Dr. Henry Wu",
195 typElementu: "employee",
196 stanowisko: "Chief Geneticist",
197 accessLevel: 5,
198 specjalizacja: ["Genetics", "Genetic engineering", "Cloning"]
199};
200employeesRepo.dodaj(naukowiec);
201
202// Sample use of generic functions
203
204// 1. Finding dinosaurs in specific enclosures
205const dinosauryWWybiegach = findAssociations<WybiegDinosaurs, DinosaurParku>(
206 enclosuresRepo.wszystkie(),
207 repoDinosaurs.wszystkie(),
208 (wybieg, dino) => wybieg.residents.includes(dino.id)
209);
210
211// 2. Generating enclosure status report
212const enclosuresReport = generujRaport<WybiegDinosaurs>(
213 "ENCLOSURE STATUS REPORT",
214 enclosuresRepo.wszystkie(),
215 wybieg => {
216 const residentsCount = wybieg.residents.length;
217 return `${wybieg.nazwa} (ID: ${wybieg.id})
218 - Environment type: ${wybieg.environmentType}
219 - Area: ${wybieg.powierzchnia} ha
220 - Fence status: ${wybieg.statusOgrodzenia}
221 - Security level: ${wybieg.securityLevel}
222 - Number of inhabitants: ${residentsCount}`;
223 }
224);
225
226// 3. Generating dinosaur health report
227const raportZdrowiaDinosaurs = generujRaport<DinosaurParku>(
228 "DINOSAUR HEALTH REPORT",
229 repoDinosaurs.wszystkie(),
230 dino => {
231 const lokalizacja = enclosuresRepo.pobierz(dino.lokalizacja)?.nazwa || "Unknown";
232 return `${dino.nazwa} (${dino.species})
233 - ID: ${dino.id}
234 - Age: ${dino.wiek} years
235 - Weight: ${dino.waga} kg
236 - Health status: ${dino.statusZdrowia}
237 - Location: ${lokalizacja}`;
238 }
239);
240
241// Displaying reports
242console.log("=== DINOSAURS IN ENCLOSURES ===");
243dinosauryWWybiegach.forEach(([wybieg, dinosaury]) => {
244 console.log(`Enclosure: ${wybieg.nazwa}`);
245 if (dinosaury.length === 0) {
246 console.log(" No inhabitants");
247 } else {
248 dinosaury.forEach(dino => {
249 console.log(` - ${dino.nazwa} (${dino.species}): ${dino.statusZdrowia}`);
250 });
251 }
252});
253
254console.log("\n" + enclosuresReport);
255console.log("\n" + raportZdrowiaDinosaurs);Generic functions are an extremely powerful tool in TypeScript that allows the creation of flexible, reusable components while maintaining full type safety. Like Dr. Wu's advanced genetic techniques, generic functions allow the creation of highly adaptive solutions that can work with different data types.
| Technique | Use in Jurassic Park | |-----------|----------------------| | Basic generic functions | Handling different types of dinosaur data | | Multi-parameter generic functions | Combining dinosaur data from different sources | | Generic constraints | Ensuring functions only work with appropriate dinosaur types | |
keyof constraints | Safe access to dinosaur properties |
| Default type parameters | Simplifying work with the most commonly used data types |
| Generic collection operations | Efficiently managing groups of dinosaurs |
| Generic classes and methods | Creating flexible specimen management systems |
| Generic interfaces and types | Defining consistent contracts for different park subsystems |Dr. Wu summarizes: "Just as our breakthrough genetic techniques allowed us to bring back to life species that disappeared from the Earth 65 million years ago, TypeScript generic functions allow programmers to create flexible, adaptive solutions that can evolve with the needs of the system. Remember however, that with great power comes great responsibility - both in genetic engineering and in programming."