/*! * Copyright (c) 2017 by The Funfix Project Developers. * Some rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { IllegalArgumentError, NotImplementedError } from "funfix-core" /** * Given a type `T` representing instances of a class `C`, the type * `Constructor` is the type of the class `C`. * * This type emulates * [Class from Flow]{@link https://flow.org/en/docs/types/utilities/#classt-a-classtoc-idtoc-class-hreftoc-classa}. * * Note that in TypeScript constructors can also be `protected` or `private` * and unfortunately specifying `{ new(): T }` is thus insufficient. * Which is why, for classes without a public constructor, we have to * specify a `_funErasure` (static) member as a property, to help the compiler * infer type `T`. * * Example: * * ```typescript * class NumBox { constructor(public num: number) {} } * class GenBox { constructor(public a: A) {} } * * function getDefault(ref: Constructor): Option { * if ((ref as any)._default) return Some(ref._default) * return None * } * * (NumBox as any)._default = new NumBox(10) * (GenBox as any)._default = new GenBox("value") * * const r1: Option = getDefault(NumBox) * const r2: Option> = getDefault(GenBox) * ``` * * And for classes with a private constructor: * * ```typescript * class PrivateBox { * private constructor(public a: A) {} * * static _funErasure: PrivateBox // leaving undefined * } * * const F = PrivateBox as any * F._default = new F("hello") * * const r: Option> = getDefault(NumBox) * ``` */ export type Constructor = { new(...args: any[]): T } | { readonly _funErasure: T } /** * The `TypeClass` interface is to be implemented by type class * definitions, exposing IDs needed for discovery management. * * Only of interest to type class authors. */ export type TypeClass = Constructor & { readonly _funTypeId: string readonly _funSupertypeIds: string[] } /** * Lightweight encoding for higher kinded types. * * Inspired by the * [Lightweight higher-kinded polymorphism]{@link https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf} * paper. * */ export interface HK { /** Trick for achieving nominal typing. */ readonly _funKindF: F /** Trick for achieving nominal typing. */ readonly _funKindA: A } /** * Data type for expressing equivalence in type class laws. * * @final */ export class Equiv { private constructor( public readonly lh: A, public readonly rh: A) {} static of(lh: A, rh: A): Equiv { return new Equiv(lh, rh) } } /** * Given a {@link TypeClass} definition in `tc`, register an `instance` * for the given {@link Constructor} specified by `c` that implements the * given type class. * * Example: * * ```typescript * registerTypeClassInstance(Functor)(Box, new BoxFunctorInstance()) * ``` * * Data types can have only one implementation for a given type class. * Multiple implementations are not allowed, therefore registration needs * to happen at most once. If registration happens multiple times for * instances of the same data type and type class, then an exception is * going to be raised: * * ```typescript * // Ok * registerTypeClassInstance(Functor)(Box, new FunctorInstance1()) * * // IllegalArgumentError: Type class coherence issue, * // Functor is already defined! * registerTypeClassInstance(Functor)(Box, new FunctorInstance1()) * ``` * * Note that type classes can have super types. So for example registering * a `Monad` instance will also register a `Functor` instance, along with * `Applicative`. The registration of supertypes however does not trigger * coherence errors. In this example, if you try registering a `Monad`, * but a `Functor` was already registered, then that the given `Monad` * will simply not be registered as a `Functor` for that data type. * * This is legal: * * ```typescript * // Ok * registerTypeClassInstance(Functor)(Box, new FunctorInstance()) * * // Ok, even though a Monad is also a Functor * registerTypeClassInstance(Functor)(Box, new MonadInstance()) * ``` * * @throws `IllegalArgumentError` in case such a type class instance * was already specified, thus leading to a coherence issue. */ export function registerTypeClassInstance(tc: TypeClass): (c: Constructor, instance: F) => void { return (c: Constructor, instance: F) => { const obj = c as any const types: {[id: string]: any} = (obj["_funTypes"] || {}) obj["_funTypes"] = types const existing = types[tc._funTypeId] if (existing) { // If trying to register the same instance, then ignore if (existing === instance) return // Coherence issue const name = existing.constructor.name throw new IllegalArgumentError( "Type class coherence issue, " + `${name}<${(c as any).name}> is already defined!` ) } types[tc._funTypeId] = instance for (const id of tc._funSupertypeIds) { if (!types[id]) types[id] = instance } } } /** * Given a {@link TypeClass} instance and a {@link Constructor} reference, * returns its associated type class implementation if it exists, or throws * a `NotImplementedError` in case there's no such association. * * ```typescript * import { Option, Functor, getTypeClass } from "funfix" * * const F: Functor> = getTypeClass(Functor, Option) * ``` */ export function getTypeClassInstance(tc: TypeClass): (c: Constructor) => F { return (c: Constructor) => { const obj = c as any const types: {[id: string]: any} = obj["_funTypes"] || {} const instance = types[tc._funTypeId] if (instance) return instance as any throw new NotImplementedError(`${(tc as any).name}<${obj.name}>`) } }