Monads are a fundamental concept in functional programming that provide a way to handle computations and data transformations in a structured manner. There are various types of monads, each designed to solve specific problems and handle different kinds of data and effects.
What is a Monad?
A monad is an abstraction that allows for the chaining of operations on wrapped values. It is defined by three primary properties:
-
Unit (also called
of
orreturn
): A function that takes a value and wraps it in a monad. -
Bind (also called
flatMap
orchain
): A function that takes a monadic value and a function that returns a monad, applies the function to the wrapped value, and returns a new monad. - Associativity: The composition of monadic operations should be associative.
Common Types of Monads
- Maybe Monad
- Either Monad
- Promise Monad
- List Monad
- Reader Monad
- Writer Monad
- State Monad
1. Maybe Monad
The Maybe Monad is used to handle optional values. It represents a computation that might fail or return null
or undefined
.
Implementation
class Maybe { constructor(value) { this.value = value; } static of(value) { return new Maybe(value); } isNothing() { return this.value === null || this.value === undefined; } map(fn) { return this.isNothing() ? this : Maybe.of(fn(this.value)); } flatMap(fn) { return this.isNothing() ? this : fn(this.value); }
} // Usage
const maybeValue = Maybe.of('hello') .map(str => str.toUpperCase()) .flatMap(str => Maybe.of(`${str} WORLD`));
console.log(maybeValue); // Maybe { value: 'HELLO WORLD' }
2. Either Monad
The Either Monad is used to handle computations that can return either a success value (Right
) or an error value (Left
).
Implementation
class Either { constructor(value, isRight = true) { this.value = value; this.isRight = isRight; } static right(value) { return new Either(value, true); } static left(value) { return new Either(value, false); } map(fn) { return this.isRight ? Either.right(fn(this.value)) : this; } flatMap(fn) { return this.isRight ? fn(this.value) : this; }
} // Usage
const rightValue = Either.right(5) .map(x => x + 1) .flatMap(x => Either.right(x * 2));
console.log(rightValue); // Either { value: 12, isRight: true } const leftValue = Either.left('error') .map(x => x + 1) .flatMap(x => Either.right(x * 2));
console.log(leftValue); // Either { value: 'error', isRight: false }
3. Promise Monad
The Promise Monad is used to handle asynchronous computations.
Usage
const fetchData = url => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`Data from ${url}`); }, 1000); });
}; // Usage
fetchData('https://api.example.com') .then(data => { console.log(data); // 'Data from https://api.example.com' return fetchData('https://api.example.com/2'); }) .then(data => { console.log(data); // 'Data from https://api.example.com/2' }) .catch(error => { console.error(error); });
4. List Monad
The List Monad is used to handle computations that produce a list of values.
Implementation
class List { constructor(values) { this.values = values; } static of(values) { return new List(values); } map(fn) { return List.of(this.values.map(fn)); } flatMap(fn) { return List.of(this.values.flatMap(value => fn(value).values)); }
} // Usage
const list = List.of([1, 2, 3]) .map(x => x + 1) .flatMap(x => List.of([x, x * 2]));
console.log(list); // List { values: [ 2, 4, 3, 6, 4, 8 ] }
5. Reader Monad
The Reader Monad is used to handle computations that depend on some shared environment or configuration.
Implementation
class Reader { constructor(fn) { this.fn = fn; } static of(value) { return new Reader(() => value); } map(fn) { return new Reader(env => fn(this.fn(env))); } flatMap(fn) { return new Reader(env => fn(this.fn(env)).fn(env)); } run(env) { return this.fn(env); }
} // Usage
const config = { baseURL: 'https://api.example.com' }; const fetchUser = new Reader(env => `${env.baseURL}/user`);
const fetchPosts = new Reader(env => `${env.baseURL}/posts`); const fetchUserAndPosts = fetchUser.flatMap(userURL => fetchPosts.map(postsURL => ({ userURL, postsURL }))
); console.log(fetchUserAndPosts.run(config)); // { userURL: 'https://api.example.com/user', postsURL: 'https://api.example.com/posts' }
6. Writer Monad
The Writer Monad is used to handle computations that produce a value along with a log or additional data.
Implementation
class Writer { constructor(value, log) { this.value = value; this.log = log; } static of(value) { return new Writer(value, ''); } map(fn) { const result = fn(this.value); return new Writer(result.value, this.log + result.log); } flatMap(fn) { const result = fn(this.value); return new Writer(result.value, this.log + result.log); } tell(log) { return new Writer(this.value, this.log + log); }
} // Usage
const writer = Writer.of(3) .map(value => new Writer(value + 1, 'Incremented\n')) .flatMap(value => new Writer(value * 2, 'Doubled\n')); console.log(writer); // Writer { value: 8, log: 'Incremented\nDoubled\n' }
7. State Monad
The State Monad is used to handle computations that maintain state.
Implementation
class State { constructor(runState) { this.runState = runState; } static of(value) { return new State(state => [value, state]); } map(fn) { return new State(state => { const [value, newState] = this.runState(state); return [fn(value), newState]; }); } flatMap(fn) { return new State(state => { const [value, newState] = this.runState(state); return fn(value).runState(newState); }); } run(initialState) { return this.runState(initialState); }
} // Usage
const increment = new State(state => [state + 1, state + 1]); const result = increment .flatMap(() => increment) .flatMap(() => increment) .run(0); console.log(result); // [3, 3]
Conclusion
Monads provide a structured and predictable way to handle computations and data transformations in functional programming. Each type of monad serves a specific purpose, from handling optional values with the Maybe Monad to managing asynchronous operations with the Promise Monad.
Discover more from Coursity
Subscribe to get the latest posts sent to your email.