Implementation of newtypes in TypeScript
npm install newtype-ts
!npm downloads
To install the stable version:
``sh`
npm i newtype-ts
Note. newtype-ts depends on fp-ts and monocle-ts, starting from 0.3.0 you must install both fp-ts and monocle-ts manually (fp-ts and monocle-ts are listed in peerDependency)
A common programming practice is to define a type whose representation is identical to an existing one but which has a separate identity in the type system.
`ts
type USD = number
type EUR = number
const myamount: USD = 1
declare function change(usd: USD): EUR
declare function saveAmount(eur: EUR): void
saveAmount(change(myamount)) // ok
saveAmount(myamount) // opss... this is also ok because both EUR and USD are type alias of number!
`
Let's define a newtype for the EUR currency
`ts
import { Newtype, iso } from 'newtype-ts'
interface EUR extends Newtype<{ readonly EUR: unique symbol }, number> {}
// isoEUR: Iso
const isoEUR = iso
// myamount: EUR
const myamount = isoEUR.wrap(0.85)
// n: number = 0.85
const n = isoEUR.unwrap(myamount)
declare function saveAmount(eur: EUR): void
saveAmount(0.85) // static error: Argument of type '0.85' is not assignable to parameter of type 'EUR'
saveAmount(myamount) // ok
`
By definition a "newtype" must have the exact same runtime representation as the value that it stores, e.g. a value of type EUR is just a number at runtime.
For the Iso type, see the monocle-ts documentation.
An Integer is a refinement of number
`ts
import { Newtype, prism } from 'newtype-ts'
interface Integer extends Newtype<{ readonly Integer: unique symbol }, number> {}
const isInteger = (n: number) => Number.isInteger(n)
// prismInteger: Prism
const prismInteger = prism
// oi: Option
const oi = prismInteger.getOption(2)
declare function f(i: Integer): void
f(2) // static error: Argument of type '2' is not assignable to parameter of type 'Integer'
oi.map(f) // ok
`
For the Prism type, see the monocle-ts documentation.
- CharInteger
- Negative
- NegativeInteger
- NonNegative
- NonNegativeInteger
- NonPositive
- NonPositiveInteger
- NonEmptyString
- NonZero
- NonZeroInteger
- Positive
- PositiveInteger
-
`ts
import { NonZero, prismNonZero } from 'newtype-ts/lib/NonZero'
// a total function
const safeDivide = (numerator: number, denominator: NonZero): number => {
return numerator / prismNonZero.reverseGet(denominator)
}
// result: Option
const result = prismNonZero.getOption(2).map(denominator => safeDivide(2, denominator))
`
The stable version is tested against TypeScript 3.5.1
| newtype-ts version | required typescript version | required fp-ts version | required monocle-ts version |
| -------------------- | ----------------------------- | ------------------------ | ----------------------------- |
| 0.3 | 3.5.1+ | 2.0.0-rc.6+ | 2.0.0-rc.1+ |
| <= 0.2.4 | 2.8+ | 1.0.0+ | 1.0.0+ |
`ts`
const double = n => n * 2
const doubleEUR = eurIso.modify(double)
Test double(2) vs doubleEUR(eurIso.wrap(2))
Results (node v8.9.3)
``
double x 538,301,203 ops/sec ±0.45% (87 runs sampled)
doubleEUR x 536,575,600 ops/sec ±0.27% (87 runs sampled)
`ts
const double = (n: number): number => n * 2
// doubleEUR: (s: EUR) => EUR
const doubleEUR = eurIso.modify(double)
``