Czas połączyć wszystkie techniki programowania funkcyjnego w jeden projekt. Zbudujemy funkcyjny system przetwarzania danych dinozaurów - od surowych odczytów sensorów po gotowe raporty bezpieczeństwa. Każdy etap to czysta funkcja, a cały proces to pipeline złożony z
pipe i compose.Nasz system składa się z kilku warstw:
1// Struktura danych wejsciowych
2const sensorReadings = [
3 { sensorId: 'S01', zone: 'A', type: 'motion', value: 85, timestamp: 1700000000 },
4 { sensorId: 'S02', zone: 'A', type: 'fence', value: 10000, timestamp: 1700000001 },
5 { sensorId: 'S03', zone: 'B', type: 'motion', value: 12, timestamp: 1700000002 },
6 { sensorId: 'S04', zone: 'B', type: 'temperature', value: 38, timestamp: 1700000003 },
7 { sensorId: null, zone: 'C', type: 'motion', value: -5, timestamp: 1700000004 },
8 { sensorId: 'S06', zone: 'A', type: 'fence', value: 0, timestamp: 1700000005 },
9];1class Maybe {
2 constructor(value) { this.value = value; }
3 static of(v) { return new Maybe(v); }
4 isNothing() { return this.value === null || this.value === undefined; }
5 map(fn) { return this.isNothing() ? this : Maybe.of(fn(this.value)); }
6 getOrElse(def) { return this.isNothing() ? def : this.value; }
7}
8
9const validateReading = (reading) => {
10 if (!reading.sensorId) return null;
11 if (reading.value < 0) return null;
12 return reading;
13};
14
15const safeValidate = (reading) =>
16 Maybe.of(reading)
17 .map(validateReading)
18 .getOrElse(null);1// Czyste funkcje - kazda robi jedna rzecz
2const isValidReading = (r) => r !== null;
3const enrichWithDate = (r) => ({
4 ...r,
5 date: new Date(r.timestamp * 1000).toISOString(),
6});
7const classifyAlert = (r) => ({
8 ...r,
9 alert: r.type === 'fence' && r.value < 5000 ? 'CRITICAL' :
10 r.type === 'motion' && r.value > 80 ? 'WARNING' : 'OK',
11});
12
13// Agregacja per strefa
14const groupByZone = (readings) =>
15 readings.reduce((groups, r) => ({
16 ...groups,
17 [r.zone]: [...(groups[r.zone] || []), r],
18 }), {});
19
20// Statystyki per strefa
21const calculateZoneStats = (grouped) =>
22 Object.entries(grouped).map(([zone, readings]) => ({
23 zone,
24 totalReadings: readings.length,
25 alerts: readings.filter((r) => r.alert !== 'OK').length,
26 avgValue: readings.reduce((s, r) => s + r.value, 0) / readings.length,
27 status: readings.some((r) => r.alert === 'CRITICAL') ? 'DANGER' : 'SAFE',
28 }));1function pipe(...fns) {
2 return (value) => fns.reduce((acc, fn) => fn(acc), value);
3}
4
5const generateSecurityReport = pipe(
6 // 1. Walidacja kazdego odczytu
7 (readings) => readings.map(safeValidate),
8 // 2. Filtrowanie niepoprawnych
9 (readings) => readings.filter(isValidReading),
10 // 3. Wzbogacanie o date
11 (readings) => readings.map(enrichWithDate),
12 // 4. Klasyfikacja alertow
13 (readings) => readings.map(classifyAlert),
14 // 5. Grupowanie per strefa
15 groupByZone,
16 // 6. Statystyki
17 calculateZoneStats,
18 // 7. Sortowanie - najniebezpieczniejsze pierwsze
19 (stats) => [...stats].sort((a, b) => b.alerts - a.alerts),
20);
21
22const report = generateSecurityReport(sensorReadings);1const filterBy = (key) => (value) => (arr) =>
2 arr.filter((item) => item[key] === value);
3
4const mapField = (key) => (fn) => (arr) =>
5 arr.map((item) => ({ ...item, [key]: fn(item[key]) }));
6
7const filterByZone = filterBy('zone');
8const filterZoneA = filterByZone('A');
9
10const normalizeValues = mapField('value')((v) => Math.round(v * 100) / 100);W edytorze poniżej znajdziesz kompletny, działający projekt do eksploracji i modyfikacji.