According to Wikipedia a monad is a design pattern that combines functions and wraps their return types in a wrapped type with additional computation.
In Layman’s terms, you take a type T, and you want to create a pipeline of chained functions performing computation on an argument of type T where each function should take into account the state of T as returned by the previous function call.
Components:
- wrapper type
- wrap function
- run function
One of the representations that I liked the most about how monads work (with credits from monad ):
There are laws that must be respected with monads (like the composition preservation law) but honestly I didn’t really care about checking that out, as long as there is a pattern here which adds value to my code.
My requirements which led to the implementation
- I wanted to access nested properties without checking for nulls at each step
- Each “monady” step would return a different T in Wrapped<T> because we are accessing properties
My implementation :
- is a simple object that contains both the wrapped type , the wrapping function and and the run function.
- has methods not part of the pattern, like ValueOrNull which are not part of the Monad pattern, by it was quite usefull.
- Has a Run (which returns the same T) and RunTransform (which returns the new T)
export class Option {
private readonly _value: T | null | undefined;
private readonly _hasValue: boolean;
public get Value(): T {
if (!this._hasValue) {
throw `This option does not have a value`;
}
return this._value;
}
public get HasValue(): boolean {
return this._hasValue;
}
constructor(value: T | null | undefined) {
if (value === null || value === undefined) {
this._hasValue = false;
this._value = null;
} else {
this._value = value;
this._hasValue = true;
}
}
public static From(value: T | null | undefined): Option {
return new Option(value);
}
public Run(execute: (value: T) => Option): Option {
return OptionRun(this, execute);
}
public RunTransform(execute: (value: T) => Option | TNew): Option {
return OptionRunTransform<T, TNew>(this, tr => {
let res = execute(tr);
if (res instanceof Option) {
return res;
} else {
return Option.From(res);
}
});
}
public ValueOr(defaultValue: T): T {
if (this._hasValue) {
return this.Value;
} else {
return defaultValue;
}
}
public ValueOrNull(): T | null{
if (this._hasValue) {
return this.Value;
} else {
return null;
}
}
}
export function OptionRun(input: Option, functor: (value: T) => Option): Option {
if (input.HasValue) {
return functor(input.Value);
}
return input;
}
export function OptionRunTransform<T, TNew>(input: Option, functor: (value: T) => Option): Option {
if (input.HasValue) {
return functor(input.Value);
}
return Option.From(null);
}
Usage
let name = Option.From(activity)
.RunTransform(r => r.ActivityTypeNode)
.RunTransform(r => r.ActivityType)
.RunTransform(r => r.Name)
.ValueOr('');
}
instead of
let name = (activity
&& activity.ActivityTYpeNode
&& activity.ActivityTypeNode.ActivityType
&& activity.ActivityTypeNode.ActivityType.Name) ?activity.ActivityTypeNode.ActivityType.Name.Name : '';
}








