Tagged unions
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.
Examples
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)
Documentation
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
union(types: Array<String>[, options: { prefix: String }]): Union
Create a tagged union. Throws if:
typesis not an array of unique strings- any
typesare named "match", "matcher", or "matches"
See safeUnion if using arbitrary strings.
Union[type]
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
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:
tagis not of any of the union typestagdoes not match any type and there is nocatchAll- any
handlerskey is not a kind in the union - any
handlersvalue 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
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
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
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".
Name
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.