gcanti / newtype-ts

Implementation of newtypes in TypeScript
https://gcanti.github.io/newtype-ts/
MIT License
582 stars 14 forks source link

Proposal for slight improvement to the definition of Newtype #26

Open derrickbeining opened 3 years ago

derrickbeining commented 3 years ago

Because Newtype is defined like this

export interface Newtype<URI, A> {
  readonly _URI: URI
  readonly _A: A
}

and the convention generally used for defining a URI is to use in interface with a unique symbol field, like this

export interface NonNegative extends Newtype<{ readonly NonNegative: unique symbol }, number> {}

TypeScript permits us to access the _URI and _A fields, even though the values don't actually exist. Everyone's just supposed to know not to do that. If instead we required the URI parameter to be a unique symbol type, we can actually make the fields for the phantom type parameters no longer accessible (as long as the unique symbol value isn't exported from the module it's defined in). We can do so like this:

declare const unique_symbol: unique symbol

type UniqueSymbol<Sym extends symbol> = symbol extends Sym ? typeof unique_symbol : symbol

export type Newtype<URI extends UniqueSymbol<URI>, A> = {
    readonly [K in URI]: A
}

With this version of Newtype, you would define NonNegative like so:

declare const NonNegativeURI: unique symbol
interface NonNegative extends Newtype<typeof NonNegativeURI, number> {}

With this type, as long as NonNegativeURI is not exported, nobody can access or even see the internal phantom field. And you can see in the TS Playground below that only unique symbols are allowed to be passed as the URI.

TS Playground example

I just wanted to share this idea in case others might consider it an improvement to the definition of Newtype and perhaps it could be implemented in newtype-ts in the future.