W zaawansowanym laboratorium Parku Jurajskiego nie zawsze mamy pewność, czy próbka DNA istnieje, czy analiza się powiodła, czy dane są kompletne. Zamiast sprawdzać warunki na każdym kroku (if/else), możemy użyć eleganckich wzorców z programowania funkcyjnego - funktorów i monad. To jak inteligentne pojemniki na próbki, które same wiedzą, jak obsłużyć brakujące lub uszkodzone dane.
Funktor to obiekt (pojemnik), który implementuje metodę
map. Pozwala stosować transformacje do wartości wewnątrz pojemnika, bez konieczności jej wyciągania.1// Najprostszy funktor - Box (pojemnik)
2class Box {
3 constructor(value) {
4 this.value = value;
5 }
6
7 map(fn) {
8 return new Box(fn(this.value));
9 }
10
11 toString() {
12 return 'Box(' + this.value + ')';
13 }
14}
15
16// Uzycie - lancuch transformacji
17const result = new Box(8000)
18 .map((weight) => weight / 1000) // Box(8)
19 .map((tons) => tons + ' tons') // Box('8 tons')
20 .map((label) => 'Weight: ' + label); // Box('Weight: 8 tons')
21
22console.log(result.value); // 'Weight: 8 tons'Monada Maybe rozwiązuje problem wartości null/undefined. Zamiast wszędzie sprawdzać
if (x !== null), owijamy wartość w Maybe i pozwalamy monadzie obsługiwać brakujące dane:1class Maybe {
2 constructor(value) {
3 this.value = value;
4 }
5
6 static of(value) {
7 return new Maybe(value);
8 }
9
10 isNothing() {
11 return this.value === null || this.value === undefined;
12 }
13
14 map(fn) {
15 return this.isNothing() ? this : Maybe.of(fn(this.value));
16 }
17
18 flatMap(fn) {
19 return this.isNothing() ? this : fn(this.value);
20 }
21
22 getOrElse(defaultValue) {
23 return this.isNothing() ? defaultValue : this.value;
24 }
25}
26
27// Bez Maybe - pelno ifow
28function getDinoZoneOld(park, dinoId) {
29 const dino = park.dinosaurs.find((d) => d.id === dinoId);
30 if (!dino) return 'Unknown';
31 const zone = park.zones.find((z) => z.id === dino.zoneId);
32 if (!zone) return 'Unknown';
33 return zone.name;
34}
35
36// Z Maybe - elegancki lancuch
37function getDinoZone(park, dinoId) {
38 return Maybe.of(park.dinosaurs.find((d) => d.id === dinoId))
39 .map((dino) => dino.zoneId)
40 .flatMap((zoneId) => Maybe.of(park.zones.find((z) => z.id === zoneId)))
41 .map((zone) => zone.name)
42 .getOrElse('Unknown');
43}Either reprezentuje wartość, która może być sukcesem (Right) lub porażką (Left). To funkcyjna alternatywa dla try/catch:
1class Left {
2 constructor(value) { this.value = value; }
3 map(fn) { return this; } // Ignoruje transformacje
4 flatMap(fn) { return this; }
5 getOrElse(defaultValue) { return defaultValue; }
6 isLeft() { return true; }
7 isRight() { return false; }
8}
9
10class Right {
11 constructor(value) { this.value = value; }
12 map(fn) { return new Right(fn(this.value)); }
13 flatMap(fn) { return fn(this.value); }
14 getOrElse(defaultValue) { return this.value; }
15 isLeft() { return false; }
16 isRight() { return true; }
17}
18
19// Uzycie w walidacji proby DNA
20function validateSample(sample) {
21 if (!sample.id) return new Left('Missing sample ID');
22 if (sample.purity < 0.5) return new Left('Purity too low: ' + sample.purity);
23 if (!sample.species) return new Left('Unknown species');
24 return new Right(sample);
25}
26
27function processSample(sample) {
28 return validateSample(sample)
29 .map((s) => ({ ...s, processed: true }))
30 .map((s) => ({ ...s, label: s.species + '-' + s.id }));
31}
32
33// Sukces
34const good = processSample({ id: 'S001', purity: 0.95, species: 'T-Rex' });
35good.getOrElse('Error'); // { id: 'S001', purity: 0.95, species: 'T-Rex', processed: true, label: 'T-Rex-S001' }
36
37// Porazka
38const bad = processSample({ id: 'S002', purity: 0.3, species: 'Raptor' });
39bad.getOrElse('Error'); // 'Error' (blad: 'Purity too low: 0.3')