https://github.com/andnp/MaybeTyped
Well-typed functional Maybe monad in typescript
https://github.com/andnp/MaybeTyped
monad typescript
Last synced: 8 months ago
JSON representation
Well-typed functional Maybe monad in typescript
- Host: GitHub
- URL: https://github.com/andnp/MaybeTyped
- Owner: andnp
- License: mit
- Created: 2018-04-12T01:18:06.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2024-09-29T08:22:12.000Z (over 1 year ago)
- Last Synced: 2025-03-17T09:18:06.989Z (10 months ago)
- Topics: monad, typescript
- Language: TypeScript
- Size: 93.8 KB
- Stars: 5
- Watchers: 3
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
- awesome-list - MaybeTyped - typed functional Maybe monad in typescript | andnp | 6 | (TypeScript)
README
# MaybeTyped
[](https://travis-ci.org/andnp/MaybeTyped)
[](https://codecov.io/gh/andnp/MaybeTyped)
[](https://github.com/semantic-release/semantic-release)
[](https://snyk.io/test/github/andnp/maybetyped?targetFile=package.json)
MaybeTyped is a well-typed Maybe (optional) monad written in typescript.
`npm install maybetyped`
## Usage Examples
```typescript
import Maybe, { some, none, maybe } from 'maybetyped';
function getUsername(): Maybe {
return maybe(usernameElement.text());
}
const normalizedUsername =
getUsername()
.map(name => name.split(' ')[0])
.map(name => name.toLowerCase())
.orElse('username');
// without Maybe
function getUsername(): string | undefined {
return usernameElement.text();
}
let normalizedUsername = 'username';
const username = getUsername();
if (username !== undefined) {
const firstName = username.split(' ')[0];
normalizedUsername = firstName.toLowerCase();
}
```
## Api
### map
Map gives access to the contained value.
Imagine an array, `Array`, as a container for strings, the `map` function applies a function to each element if the container is not empty and gives back a new container.
For instance:
```typescript
const orig: Array = ['1', '2', '3'];
const now: Array = orig.map(x => parseInt(x));
```
```typescript
some('thing').map(v => console.log(v)) // prints "thing"
none().map(v => console.log(v)) // does not print
```
### tap
Like `map`, it calls a function with the current value, if there is one, but ignores any return value and the result is always the original Maybe. Intended for running side-effects, without changing the value.
```typescript
some(1)
// If this was `.map`, then the result of this call would be None
.tap(x => console.log(`Original value is ${x}`))
.map(x => x + 1)
.tap(x => console.log(`New value is ${x}`))
```
### flatMap
FlatMap also accesses the contained value, but it expects that its "mapper" function returns a container of the same type.
Imagine the conceptually equivalent array container:
```typescript
const orig: Array = [1, 3, 5];
const now: Array = orig.flatMap(x => ( [x, x + 1] ));
console.log(now); // => [1, 2, 3, 4, 5, 6]
```
```typescript
const maybeAdd1 = (x: Maybe) => x.map(y => y + 1);
const x = some(2).flatMap(maybeAdd1); // Maybe<3>
const y = none().flatMap(maybeAdd1); // Maybe
```
### or
Similar to the `or` logical operator.
Tries to get the value (true) of the first maybe; if it is empty (false), tries to get the value (true) of the second maybe.
If both are empty (false), returns an empty (false) maybe.
```typescript
const first = none();
const second = some(22);
const third = first.or(second); // Maybe<22>
```
### orElse
Similar to `or`, except the second value is not allowed to be empty.
`orElse` _must_ return an instance of the contained value, even if the maybe is empty.
This is useful for supplying default values:
```typescript
const maybeName = maybe(getNameFromInput());
const name = maybeName.orElse('enter name please');
```
```typescript
const first = none();
const second = 'hi';
const third = first.orElse(second); // 'hi';
```
### expect
`expect` forcefully gets the value out of the `Maybe` container, or throws an error if there is no value.
This is useful whenever you _know_ the value must be defined at this point, and you want to get out of the `Maybe` chain.
For instance:
```typescript
function tryOption1(): Maybe { ... }
function tryOption2(): Maybe { ... }
function tryOption3(): Maybe { ... } // The string must be created by one of these 3, we just don't know which
const str: string =
tryOption1()
.or(tryOption2)
.or(tryOption3)
.expect('We expected to get the from one of these three methods');
```
```typescript
function getData(): Maybe { ... }
const maybeData = getData();
const shouldHaveData = maybeData.expect("oops, guess I didn't");
// throws an error with the given message if value is null
// otherwise returns value
```
### caseOf
`caseOf` is a pattern matcher for the `Maybe`.
This is useful when you want to execute different logic dependent on whether the container is empty.
For instance:
```typescript
maybeData.caseOf({
none: () => attemptToGetFromApi().map(doThingWithData),
some: data => doThingWithData(data),
});
```
```typescript
getData().caseOf({
some: value => value + 1,
none: () => 1,
});
// executes the "some" function if not null
// executes the "none" function if null
```
### asNullable
`asNullable` provides an "out" for escaping the `Maybe` container.
This is particularly useful at the boundaries of your API.
Often the internals of a library use `Maybe` to clean up code, but would like their external contracts to not be forced to use `Maybe`s, but instead "vanilla" JS.
For instance:
```typescript
export function doThing(): string | undefined {
const maybeValue: Maybe = getFromSomewhereInLib();
return maybeValue.asNullable();
}
```
```typescript
const value = 'hi';
const nullable = maybe(value).asNullable();
assert(nullable === value);
```
### join
`join` takes a "joiner" function and another `Maybe` instance and combines them.
If either of the `Maybe`s are empty, then the joiner function is not called.
```typescript
const first = maybe(getFirstName());
const last = maybe(getLastName());
const name_007 = first.join(
(a, b) => `${b}. ${a} ${b}.`,
last,
);
```
## MaybeT
```typescript
export function apiUserSearch(user: string): MaybeT> {
// if user does not exist, api returns undefined
return maybeT(fetch(`some/uri?user=${user}`).json());
}
const userBirthday = await apiUserSearch('yagami')
.map(user => user.birthday)
.map(date => new Date(date))
.orElse(() => Date.now()); // <- this is probably a bad design choice :P
const userBirthdayPromises = maybeT(['misa misa', 'light', null, 'ryuk'])
.map(apiUserSearch)
.map(maybeUser =>
maybeUser
.map(user => user.birthday)
.map(date => new Date(date))
.orElse(() => Date.now()))
.asNullable();
const userBirthdays = await Promise.all(userBirthdayPromises);
```
## Api
### maybeT
`maybeT` is the constructor for a maybe transform.
Anything with a `map` function can be transformed into a `maybeT`.
Due to the commonality of the use case, support for `thenables` is also added, though be warned that `then` matches `flatMap` semantics, not `map` semantics.
```typescript
const maybeThings = maybeT([1, 2, null, 4, undefined, 6]); // MaybeT>
const maybeLater = maybeT(Promise.resolve('hey')); // MaybeT>
```
### map
```typescript
const things = maybeT(['1', '2', null, '4']) // MaybeT>
.map(x => parseInt(x)); // MaybeT>
```
### caseOf
```typescript
const things = maybeT([1, 2, null, 4])
.caseOf({
none: () => 4,
some: x => x + 1,
}); // MaybeT> => MaybeT<[2, 3, 4, 5]>
```
### orElse
```typescript
const things = maybeT([1, 2, null, 4])
.orElse(3); // MaybeT> => MaybeT<[1, 2, 3, 4]>
```
### asNullable
```typescript
const things = maybeT([1, 2, null, 4])
.asNullable(); // Array => [1, 2, null, 4]
```
### asType
Because typescript does not have support for higher-kinded-types (HKT), we lose track of which monad-like HKT we are dealing with (`Array` or `Promise` or other).
This means that after most operations the type will become `MaybeT>`.
To cope with this, we provide an `asType` method that will allow us to properly "remember" what type of monad we were originally dealing with.
A little type safety will be lost here, as you could lie and say this is an `Array` instead of a `Promise`, but the constructor that is passed in to this method will confirm the type at runtime.
This method also asks for the contained type, but because we _haven't_ forgotten that, we will be able to check that.
Programmatic examples below should help make this more clear.
```typescript
const a = maybeT(Promise.resolve('hi'))
.asType>(Promise); // Promise => this is correct
const b = maybeT(Promise.resolve('hey'))
.asType>(Array); // Array => this will throw a runtime err, but not a compile err
const c = maybeT(Promise.resolve('hello'))
.asType>(Promise); // any => this will throw a compile err, but not runtime
const d = maybeT(Promise.resolve('merp'))
.asType>(Array); // any => this will throw a compile err and runtime
```