We use cookies to enhance your experience on the site
CodeWorlds

FP Patterns: Functors and Monads

In the advanced laboratory of Jurassic Park, we don't always know whether a DNA sample exists, whether the analysis succeeded, or whether the data is complete. Instead of checking conditions at every step (if/else), we can use elegant patterns from functional programming - functors and monads. They are like intelligent sample containers that know how to handle missing or damaged data on their own.

Functors - Containers with Mapping

A functor is an object (container) that implements a

map
method. It allows applying transformations to the value inside the container without having to extract it.

1// The simplest functor - Box (container)
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// Usage - chain of transformations
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'

Maybe - Handling Missing Values

The Maybe monad solves the problem of null/undefined values. Instead of checking

if (x !== null)
everywhere, we wrap the value in a Maybe and let the monad handle missing data:

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// Without Maybe - full of ifs
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// With Maybe - elegant chain
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 - Error Handling

Either represents a value that can be a success (Right) or a failure (Left). It's a functional alternative to try/catch:

1class Left {
2  constructor(value) { this.value = value; }
3  map(fn) { return this; }           // Ignores transformations
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// Usage in DNA sample validation
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// Success
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// Failure
38const bad = processSample({ id: 'S002', purity: 0.3, species: 'Raptor' });
39bad.getOrElse('Error'); // 'Error' (error: 'Purity too low: 0.3')
Go to CodeWorlds