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

@nprindle/minewt

v1.1.0

Published

Minimal newtypes for TypeScript

Downloads

12

Readme

minewt

Travis CI badge

A tiny newtype implementation for TypeScript.

Thanks to MayMoonsley for the very punny name :3

Motivation

Type aliases are a very common tool in TypeScript for type abstraction. They make it easy to assign names to more complex types, and can even be used for some degree of type-level programming. However, sometimes, something like the following crops up:

type Dollars = number;

The name Dollars is now an alias for number; now, instead of writing pay(amount: number), we can write pay(amount: Dollars). However, this does not create a new type; it is not a compile error to pass in any old number into pay. At best, it serves as documentation for programmers reading the code, but it doesn't provide any type safety.

Another approach is to take the Java route, and make a new object to wrap numbers:

class Dollars {
    constructor(public readonly amount: number) {}
}

Now, we can convert to and from this new type via the constructor and field:

const dollars: Dollars = new Dollars(3);
const amount: number = dollars.amount;

This has the type safety we want; Dollars is a distinct type from number! Unfortunately, this creates a new object for every Dollars we have, and incurs a boxing penalty. What we really want is to create a type with the same runtime representation as number, but create it as a distinct type, with neither assignable to the other.

A newtype is the best of both worlds. A newtype:

  • is a distinct type from its underlying type
  • has the same runtime representation as its underlying type

After compilation, a newtype will be exactly the same as its underlying type; all values of type Dollars will be numbers at runtime. It's safe to cast between the two, since they have exactly the same representation. This makes newtypes a kind of zero-cost abstraction.

Usage

To create the type and the wrapper:

// Create the newtype itself
type Dollars = Newtype<number, { readonly _: unique symbol; }>;

// Create the newtype wrapper function
const Dollars = newtype<Dollars>();

To wrap and unwrap values:

// Wrap underlying type in newtype
const dollars: Dollars = Dollars(3);
// Unwrap newtype into underlying type
const amount: number = unwrap(dollars);

// A newtype creates a distinct type:
const x: Dollars = 3; // Error! Type '3' is not assignable to type 'Dollars'

To query the underlying representation type, use NewtypeRepr:

type T = NewtypeRepr<Dollars>; // 'number'

You can also lift functions over the underlying type to functions over the wrapped type using liftN and liftN2 (higher-arity lifting functions are easy to define if necessary):

function add(x: number, y: number): number {
    return x + y;
}

const x: Dollars = ...;
const y: Dollars = ...;

const addDollars = liftN2<Dollars>(add);

const sum = addDollars(x, y);

It's even possible to make newtypes that are assignable to other newtypes, using intersection types with &:

type A = Newtype<number, { readonly _: unique symbol; }>;
const A = newtype<A>();

// B extends A
type B = Newtype<number, { readonly _: unique symbol; }> & A;
const B = newtype<B>();

const x: A = A(0); // okay
const y: A = B(0); // okay
const z: B = A(0); // compile error: Type 'A' is not assignable to type 'B'

Comparisons and Alternatives

minewt is very lightweight, and doesn't provide any specific newtype implementations.

Other newtype implementations

  • newtype-ts: uses a similar approach, but heavier weight, as it's tightly integrated with functional optics as part of the fp-ts community

Other solutions

Another solution is to use tag types, which are a different zero-cost abstraction with a somewhat similar purpose to newtypes. You can use tag types to add information to a type, such as refinement information:

function isEmail(str: string): str is string & Email {
    // ...
}

Multiple tags can also be added to a single type. In contrast to newtypes, tag types have a slightly different goal. Tag types are mostly geared towards validation, like the example above, and adding tags is mostly done via type predicates. Tags can generally be added to any type.

Some neat libraries that implement tag types:

  • taghiro: implements tag types, and also ships with many useful tags