ADTs for TypeScript
npm install @rorystokes/ad-tsBringing Pattern Matching and other ADT and Functional Programming concepts to TypeScript. Borrows
heavily from and builds on top of @gcanti's fp-ts library.
match submodule provides simple, type-safe 'pattern matching' across a TaggedUnion. This requires each instance of any of_tag value to uniquely identify the type they belongMany of the types provided by fp-ts including Option and Either
already have such tags, and as such a TaggedUnion can be constructed using these types.
TaggedUnions must be used instead of native TypeScript Unions as they encode the mapping from tags
to types in a form that the type system can leverage to provide safety in match methods.
caseclass submodule is designed to supplement the usage of match, providing an easy way topatternmatch submodule allows for more generic pattern matching across any values. It alwaysfp-ts Option, as exhaustivity testing is not Patterns can either be simple boolean guards, or extractor methods that return optional extracted
results.
do submodule aims to provide equivalent syntax to for comprehensions in Scala or do in This depends heavily on the Monad HKT defined by fp-ts.
ts
import { doOn } from '@rorystokes/ad-ts/do';
import { Either, either, left, right } from 'fp-ts/lib/Either';const numberOrError = (s: string): Either => {
const n = Number.parseFloat(s)
return Number.isNaN(n) ? left(
'${s}' is not a number) : right(n)
}const sqrtOrError = (n: number): Either => {
const sqrt = Math.sqrt(n)
return Number.isInteger(sqrt) ? right(sqrt) : left(
${n} is not a square number])
}const testString = (s: string) => doOn(either)
.with("s", s)
.bind("n", ({s}) => numberOrError(s))
.bind("sqrt", ({n}) => sqrtOrError(n))
.yield(({sqrt}) => sqrt)
testString("9") // right(3)
testString("8") // left("8 is not a square number")
testString("seven") // left("'seven' is not a number")
`$3
`ts
import { match, TaggedUnion, TypeTag } from '@rorystokes/ad-ts/match'class Foo {
readonly [TypeTag]: "Foo"
constructor(readonly name: string) { }
}
class Bar {
readonly [TypeTag]: "Bar"
constructor(readonly fn: string, readonly sn: string) { }
}
type FooBar = TaggedUnion<{
Foo: Foo,
Bar: Bar
}>
const getName = (foobar: FooBar) => match(foobar)({
Foo: ({ name }) => name,
Bar: ({ fn, sn }) =>
${fn} ${sn}
})getName(new Foo("Alan")) // "Alan"
getName(new Bar("Bob", "Brown")) // "Bob Brown"
`$3
`ts
import { match, TaggedUnion } from '@rorystokes/ad-ts/match'
import { CaseClass, Default } from '@rorystokes/ad-ts/caseclass'
const Foo = CaseClass("Foo")<{
name: string,
size?: number
}>()
type Foo = ReturnType
const Bar = CaseClass("Bar")<{
label: string,
prefix: Default
}>({
prefix: "Bar:"
})
type Bar = ReturnType
type FooBar = TaggedUnion<{
Foo: Foo,
Bar: Bar
}>
const getName = (foobar: FooBar) => match(foobar)({
Foo: ({ name }) => name,
Bar: ({ label, prefix }) =>
${prefix} ${label}
})
getName(Foo({ name: "Alan" })) // "Alan"
getName(Bar({ label: "Bob" })) // "Bar: Bob"
getName(Bar({ label: "Carol", prefix: ">>" })) // ">> Carol"
`$3
`ts
import { patternmatch, Pattern } from '@rorystokes/ad-ts/patternmatch'
import { none, some } from 'fp-ts/lib/Option';const IsInteger: Pattern = Number.isInteger
const Square: Pattern = (i: number) => {
const sqrt = Math.sqrt(i)
return Number.isInteger(sqrt) ? some({ i, sqrt }) : none
}
const checkNumber = patternmatch({ Square, IsInteger })({
Square: ({ sqrt, i }) =>
${i} is ${sqrt}^2,
IsInteger: (i) => ${i} is an integer
})checkNumber(3) // some("3 is an integer")
checkNumber(4) // some("4 is 2^2")
checkNumber(5.6) // none
`With fp-ts
See https://github.com/gcanti/fp-ts$3
`ts
import { match, TaggedUnion } from '@rorystokes/ad-ts/match'
import { Some, None, some, none } from 'fp-ts/lib/Option'type TaggedOption = TaggedUnion<{
Some: Some,
None: None
}>
const valueOrEmpty = (opt: TaggedOption) => match(opt)({
Some: ({value}) => value,
None: () => "Empty"
})
valueOrEmpty(some("Full")) // "Full"
valueOrEmpty(none) // "Empty"
`$3
`ts
import { match, TaggedUnion } from '@rorystokes/ad-ts/match'
import { Left, Right, left, right } from 'fp-ts/lib/Either'type TaggedEither = TaggedUnion<{
Left: Left,
Right: Right
}>
const getString = (either: TaggedEither) => match(either)({
Left: ({value}) =>
#${value},
Right: ({value}) => '${value}'
})
getString(left(42)) // "#42"
getString(right("hello")) // "'hello'"
``