npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

maybetyped

v1.6.0

Published

Well-typed functional Maybe monad

Downloads

802

Readme

MaybeTyped

Build Status codecov semantic-release Known Vulnerabilities Greenkeeper badge

MaybeTyped is a well-typed Maybe (optional) monad written in typescript.

npm install maybetyped

Usage Examples

import Maybe, { some, none, maybe } from 'maybetyped';

function getUsername(): Maybe<string> {
    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<string>, 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:

const orig: Array<string> = ['1', '2', '3'];
const now: Array<number> = orig.map(x => parseInt(x));
    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.

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:

const orig: Array<number> = [1, 3, 5];
const now: Array<number> = orig.flatMap(x => ( [x, x + 1] ));
console.log(now); // => [1, 2, 3, 4, 5, 6]
const maybeAdd1 = (x: Maybe<number>) => x.map(y => y + 1);

const x = some(2).flatMap(maybeAdd1); // Maybe<3>

const y = none().flatMap(maybeAdd1); // Maybe<undefined>

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.

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:

const maybeName = maybe(getNameFromInput());
const name = maybeName.orElse('enter name please');
const first = none<string>();
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:

function tryOption1(): Maybe<string> { ... }
function tryOption2(): Maybe<string> { ... }
function tryOption3(): Maybe<string> { ... } // 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');
function getData(): Maybe<DataType> { ... }
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:

maybeData.caseOf({
    none: () => attemptToGetFromApi().map(doThingWithData),
    some: data => doThingWithData(data),
});
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 Maybes, but instead "vanilla" JS. For instance:

export function doThing(): string | undefined {
    const maybeValue: Maybe<string> = getFromSomewhereInLib();
    return maybeValue.asNullable();
}
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 Maybes are empty, then the joiner function is not called.

const first = maybe(getFirstName());
const last = maybe(getLastName());

const name_007 = first.join(
    (a, b) => `${b}. ${a} ${b}.`,
    last,
);

MaybeT

export function apiUserSearch(user: string): MaybeT<Promise<UserData>> {
    // 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.

const maybeThings = maybeT([1, 2, null, 4, undefined, 6]); // MaybeT<Array<number>>
const maybeLater = maybeT(Promise.resolve('hey')); // MaybeT<Promise<string>>

map

const things = maybeT(['1', '2', null, '4']) // MaybeT<Array<string>>
    .map(x => parseInt(x)); // MaybeT<Array<number>>

caseOf

const things = maybeT([1, 2, null, 4])
    .caseOf({
        none: () => 4,
        some: x => x + 1,
    }); // MaybeT<Array<number>> => MaybeT<[2, 3, 4, 5]>

orElse

const things = maybeT([1, 2, null, 4])
    .orElse(3); // MaybeT<Array<number>> => MaybeT<[1, 2, 3, 4]>

asNullable

const things = maybeT([1, 2, null, 4])
    .asNullable(); // Array<number> => [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<MonadLike<*>>. 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.

const a = maybeT(Promise.resolve('hi'))
    .asType<Promise<string>>(Promise); // Promise<string> => this is correct

const b = maybeT(Promise.resolve('hey'))
    .asType<Array<string>>(Array); // Array<string> => this will throw a runtime err, but not a compile err

const c = maybeT(Promise.resolve('hello'))
    .asType<Promise<number>>(Promise); // any => this will throw a compile err, but not runtime

const d = maybeT(Promise.resolve('merp'))
    .asType<Promise<string>>(Array); // any => this will throw a compile err and runtime