Skip to content

Instantly share code, notes, and snippets.

@doorgan
Last active June 1, 2020 07:12
Show Gist options
  • Save doorgan/64de31fd8b6ac1a844941aec5ec95c22 to your computer and use it in GitHub Desktop.
Save doorgan/64de31fd8b6ac1a844941aec5ec95c22 to your computer and use it in GitHub Desktop.
Daggy's typed unions
// @ts-check
import union from "./union.js"
const Option = union("Option", {
Some: ["value"],
None: []
});
const val = Option.Some(5)
val.match({
Some: value => console.log("Got value", value),
None: () => console.log("Got nothing!")
})
val.match({
Some: () => {}
// Property 'None' is missing in type '{ Some: () => void; }' but required
// in type '{ Some: Function; None: Function; }'
})
// @ts-check
import {taggedSum} from "daggy";
/**
* An object mapping the keys of `T` to a function, such that the match function
* can make a static exhaustive check
*
* @template T
* @typedef MatchClauses
* @type {{[K in keyof T]: Function}}
*/
/**
* The functiosn declared in the prototype of a Union.
*
* @template T
*
* @typedef UnionProtoFuns
* @type {Object}
*
* @prop {(any) => any} cata
* Same as match
*
* @prop {toString} toString
* Returns a stringified version of the type.
*
* @prop {(clauses: MatchClauses<T>) => any} match
* Matches on the union type to branch the logic. Each branch takes a function
* with the union values as arguments(in the order they were defined), that
* will be evaluated if that branch matches the union's type.
*
* Match enforces exchaustiveness checking, so there must be a branch for every
* type, otherwise match will raise an error.
*/
/**
* The functions that are part of the Union constructor
* @template T
* @typedef UnionFuns
* @type {Object}
*
* @prop {() => string} toString
* Returns a stringified version of the type.
*
* @prop {(constructor: ConcreteUnion<T>) => boolean} is
* Checks if the value's tipe matches the given constructor
*/
/**
* The function used to instantiate a concrete union type.
*
* @template T
* @callback UnionConstructor
* @param {...any} [args]
* @returns {ConcreteUnion<T>}
*/
/**
* @template T
* @typedef ConcreteUnion
* @type {UnionFuns<T> & UnionProtoFuns<T>}
*/
/**
* @template T
* @typedef UnionType
* @type {UnionConstructor<T> & ConcreteUnion<T>}
*/
/**
* @template T
* @typedef {{[K in keyof T]: UnionType<T>} & UnionFuns<T>} Union
*/
/**
* Returns Type Representative containing constructors of for each key in
* constructors as a property.
*
* @template T
* @param {string} name
* @param {T} constructors
* @returns {Union<T>}
*/
export default function union(name, constructors) {
let tagged = taggedSum(name, constructors);
tagged.prototype.match = function(clauses) { return this.cata(clauses)}
/**
* Typescript is not powerful enough to understand the tricks daggy uses
* to construct the tagged sum, so I need it to ignore the return value
* to make it use the above type declarations instead.
*/
// @ts-ignore
return tagged;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment