Tagged unions
npm install tagmeme
Tagmeme is a library for building tagged unions. This project offers:
- A concise API for pattern matching and variant constructors
- Data-oriented tags that can be serialized and namespaced
- Errors in development to ensure exhaustive pattern matching
- Small size and zero-dependencies in production using dead code elimination
Let's check it out.
import assert from 'assert'
import { union } from 'tagmeme'
const Result = union(['Ok', 'Err'])
const err = Result.Err(new Error('My error'))
const message = Result.match(err, {
Ok: () => 'No error',
Err: error => error.message
})
assert(message === 'My error')
const handle = Result.matcher({
Ok: () => 'No error',
Err: error => error.message
})
assert(handle(err) === 'My error')
const isError = Result.matches(err, Result.Err)
assert(isError)
This package includes:
union(types, options)
Union[type](data)
Union.match(tag, handlers, catchAll)
Union.matcher(handlers, catchAll)
Union.matches(tag, variant)
safeUnion(types, options)
union(types: Array<String>[, options: { prefix: String }]): Union
Create a tagged union. Throws if:
types
is not an array of unique strings- any
types
are named "match", "matcher", or "matches"
See safeUnion
if using arbitrary strings.
Union[type](data: any): ({ type, data })
Create a tag of the union containing data
which can be retrieved via Union.match
.
import assert from 'assert'
import { union } from 'tagmeme'
const Result = union(['Ok', 'Err'])
const result = Result.Ok('good stuff')
assert(result.type === 'Ok')
assert(result.data === 'good stuff')
Union.match(tag, handlers[, catchAll: function])
Pattern match on tag
with a hashmap of handlers
where keys are kinds and values are functions, with an optional catchAll
if no handler matches the value.
Throws if:
tag
is not of any of the union typestag
does not match any type and there is nocatchAll
- any
handlers
key is not a kind in the union - any
handlers
value is not a function - it handles all cases and there is a useless
catchAll
- it does not handle all cases and there is no
catchAll
import assert from 'assert'
import { union } from 'tagmeme'
const Result = union(['Ok', 'Err'])
const result = Result.Err('Request failed')
const status = Result.match(
result,
{ Err: () => 400 },
() => 200
})
assert(status === 400)
Union.matcher(handlers[, catchAll: function])
Create a matching function which will take tag
and context
arguments.
This reduces the boilerplate of a function that delegates to Union.match
with static handlers.
This is also a bit faster than match
because the handler functions only need to be created once.
Unlike with match
, the second argument to handlers will be context
to avoid the need for a closure.
import assert from 'assert'
import { union } from 'tagmeme'
const Result = union(['Ok', 'Err'])
const collectErrors = Result.matcher({
Ok: (_, errors) => errors,
Err: (error, errors) => errors.concat(error)
})
const errors = collectErrors(Result.Err('Bad'), [])
assert.deepEqual(errors, ['Bad'])
Union.matches(tag, variant: Variant): Boolean
Determine whether a given tag
is of variant
.
import assert from 'assert'
import { union } from 'tagmeme'
const Result = union(['Ok', 'Err'])
const okTag = Result.Ok(1)
assert(Result.matches(okTag, Result.Ok))
safeUnion(types: Array<String>[, options: { prefix: String }]): { methods, variants }
For library authors accepting arbitrary strings for type names, safeUnion
is union
but returns distinct collections of methods and type variants.
This will not throw if a type is "match", "matcher", or "matches".
tagmeme |ˈtaɡmiːm|: a slot in a syntactic frame which may be filled by any member of a set of appropriate linguistic items.
This name is kind of fitting for a tagged union library.