UNPKG

effect-ts-laws

Version:
191 lines 9.83 kB
import type { LiftArbitrary } from '#arbitrary'; import type { LawSet, LiftEquivalence } from '#law'; import { Monoid as MO } from '@effect/typeclass'; import { Equivalence as EQ, Option as OP, Predicate as PR } from 'effect'; import type { Kind, TypeLambda } from 'effect/HKT'; import fc from 'fast-check'; /** * Options for testing * [parameterized-type](https://github.com/Effect-TS/effect/blob/main/packages/typeclass/README.md#parameterized-types) * typeclasses. All the typeclass laws here expect their arguments to be of * this type. * @typeParam Typeclass - The type lambda of the typeclass under tests. For * example when testing an instance of `Covariant<Array<string>>`, `Typeclass` * would be the type lambda of the higher-kinded type `Covariant`. * @typeParam F - The type lambda of the datatype under test. For example when * testing an instance of `Covariant<Array<string>>`, `F` would be the * type lambda of the higher-kinded type `Array`. * @category harness */ export interface ParameterizedGiven<Typeclass extends TypeLambda, F1 extends TypeLambda, A, B = A, C = A, R = never, O = unknown, E = unknown> extends GivenConcerns<F1, A, B, C, R, O, E> { /** * The higher-kinded type `Typeclass<F>` is the typeclass instance under test. * For example when testing the `Monad` laws on an `Either<number, string>`, * this would be `Monad<Either>`. */ F: Kind<Typeclass, R, O, E, F1>; } /** * Unfolded requirements for testing parameterized typeclass laws. * These are set as the union of all arguments required by the law predicates * for parameterized typeclass laws. * * For example, the field `aob` is required by the * {@link filterableLaws} _composition_ typeclass law, and `bab` by * the the * _reduce_ law of {@link foldableLaws}. * * The nomenclature is based on the types of expressions, and is designed to * make it easy, or at least easy as possible, for the author of law tests * to find the correct arguments required by the predicates they are checking: * * 1. `A`, `B`, and `C` are the underlying types. There are no predicates in any * parameterized typeclass law that require _more_ than three type parameters. * 2. Arbitraries * 1. Lower case `a`, `b`, and `c` are the names of arbitraries for the three * underlying types. * 2. The arbitraries for the data type under test are called `fa`, `fb`, * and `fc`, standing for arbitraries of `F<A>`, `F<B>`, and `F<C>` * respectively. * 3. For simple unary arbitrary functions between two different underlying * types, for example `A ⇒ B`, we use the lower case type names of argument * followed by return value. For example an arbitrary for a function of type * `C ⇒ B` we use the field name `cb`. * 4. Binary functions use the same pattern, so an arbitrary for a function * of type `(b: B, a: A) => B` will be called `bab`. * 5. Unary function from a type to itself are named `endo[TYPE]`, for * example `endoA` is the name of an arbitrary a function `A ⇒ A`. * 6. Arbitraries for functions that lift an underlying type into the data * type under test, also use the same naming pattern, except the return type * is prefixed with a lowercase `f`. For example, `afb` is an arbitrary for * a function of the type `A ⇒ F<B>`, where `F` is the higher-kinded * datatype under test. * 7. Arbitraries that lift an underlying type into the `Option` data type * are also named in this way, except the `f` is replaced by an `o`. `aob`, * for example, is the field where you will find an arbitrary function of * type `A ⇒ Option<B>`. * 8. `Applicative` laws, for example, require a function of the type: * `(of: <T>(a: T) => F<T>>) => Arbitrary<F<(a: A) => B>>>`. I.e.: * a function tht takes a _lifting_ function, and returns an arbitrary * for the unary function `(a: A) => B`. These are called just like * the lifted function argument would have been called (`afb`), but with * an `Of` suffix, and the `f` is moved to the head position. The function * above, for example, would be found in a field called `fabOf`. * 3. Equivalences * 1. The equivalences for the underlying types are called `equalsA`, * `equalsB`, and `equalsC`. * 2. The equivalences for the data type under test are called `equalsFa`, * `equalsFb`, and `equalsFc`. * 4. For laws that require predicates, we have fields `predicateA`, * `predicateB`, and `predicateC`, for functions that take a value of the * underlying type and returns a boolean. * * Having _all_ given arguments for all predicates in a central place is less * [open-closed](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle). * We still do it though, as it massively simplifies the code unfolding * law predicate arguments. * @typeParam Typeclass - The type lambda of the typeclass under tests. For * example when testing an instance of `Covariant<Array<string>>`, `Typeclass` * would be the type lambda of the higher-kinded type `Covariant`. * @typeParam F - The type lambda of the datatype under test. For example when * testing an instance of `Covariant<Array<string>>`, `F` would be the * type lambda of the higher-kinded type `Array`. * @typeParam A - First underlying type for the typeclass tests. * @typeParam B - second underlying type for the typeclass tests. * @typeParam C - Third underlying type for the typeclass tests. _Three_ is the * maximum number of type parameters required by any predicate of the parameterized * typeclass laws. */ export interface UnfoldedGiven<Typeclass extends TypeLambda, F1 extends TypeLambda, A, B = A, C = A, R = never, O = unknown, E = unknown> extends ParameterizedGiven<Typeclass, F1, A, B, C, R, O, E> { equalsFa: EQ.Equivalence<Kind<F1, R, O, E, A>>; equalsFb: EQ.Equivalence<Kind<F1, R, O, E, B>>; equalsFc: EQ.Equivalence<Kind<F1, R, O, E, C>>; fa: fc.Arbitrary<Kind<F1, R, O, E, A>>; fb: fc.Arbitrary<Kind<F1, R, O, E, B>>; fc: fc.Arbitrary<Kind<F1, R, O, E, C>>; endoA: fc.Arbitrary<(a: A) => A>; ab: fc.Arbitrary<(a: A) => B>; bc: fc.Arbitrary<(a: B) => C>; ba: fc.Arbitrary<(a: B) => A>; cb: fc.Arbitrary<(a: C) => B>; bab: fc.Arbitrary<(b: B, a: A) => B>; afb: fc.Arbitrary<(a: A) => Kind<F1, R, O, E, B>>; bfc: fc.Arbitrary<(b: B) => Kind<F1, R, O, E, C>>; aob: fc.Arbitrary<(a: A) => OP.Option<B>>; boc: fc.Arbitrary<(a: B) => OP.Option<C>>; fabOf: (of: <T>(a: T) => Kind<F1, R, O, E, T>) => fc.Arbitrary<Kind<F1, R, O, E, (a: A) => B>>; fbcOf: (of: <T>(a: T) => Kind<F1, R, O, E, T>) => fc.Arbitrary<Kind<F1, R, O, E, (a: B) => C>>; } /** * Unfold a {@link ParameterizedGiven} into all arguments required by predicates * of parameterized typeclass laws. * @param given - The options to unfold. * @category harness */ export declare const unfoldGiven: <Typeclass extends TypeLambda, F extends TypeLambda, A, B = A, C = A, R = never, O = unknown, E = unknown>(given: ParameterizedGiven<Typeclass, F, A, B, C, R, O, E>) => UnfoldedGiven<Typeclass, F, A, B, C, R, O, E>; /** * Use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) * to add entries here for a new typeclasses. Used to map from typeclass * name to the typeclass type lambda. * @internal * @category model */ export interface ParameterizedLambdas { } /** * The _equivalence_ and _arbitrary_ concerns of typeclass test options, * together with an optional `Monoid` for the underlying type `A` * * Everything required to build laws for a typeclass except the instances under * test. * @category model */ export interface GivenConcerns<F extends TypeLambda, A, B = A, C = A, R = never, O = unknown, E = unknown> extends GivenArbitraries<F, A, B, C, R, O, E>, GivenEquivalence<F, A, B, C, R, O, E>, GivenPredicates<A, B, C> { /** * Required `Monoid` for the underlying type `A`, useful for typeclasses * like `Applicative` that can build their own `Monoid` instance from it. */ Monoid: MO.Monoid<A>; } interface GivenEquivalence<F extends TypeLambda, A, B, C, R, O, E> { /** An equivalence for the underlying type `A`. */ equalsA: EQ.Equivalence<A>; /** An equivalence for the underlying type `B`. */ equalsB: EQ.Equivalence<B>; /** An equivalence for the underlying type `C`. */ equalsC: EQ.Equivalence<C>; /** * A function that will get an equivalence for the type under test from an * equivalence for the underlying type. */ getEquivalence: LiftEquivalence<F, R, O, E>; } export interface GivenArbitraries<F extends TypeLambda, A, B, C, R, O, E> { /** An arbitrary for the underlying type `A`. */ a: fc.Arbitrary<A>; /** An arbitrary for the underlying type `B`. */ b: fc.Arbitrary<B>; /** An arbitrary for the underlying type `C`. */ c: fc.Arbitrary<C>; /** * A function that will get an arbitrary for the type under test from an * arbitrary for the underlying type. */ getArbitrary: LiftArbitrary<F, R, O, E>; } interface GivenPredicates<A, B, C> { /** Predicate for values of type `A`. */ predicateA: fc.Arbitrary<PR.Predicate<A>>; /** Predicate for values of type `B`. */ predicateB: fc.Arbitrary<PR.Predicate<B>>; /** Predicate for values of type `C`. */ predicateC: fc.Arbitrary<PR.Predicate<C>>; } /** * Interface for functions that build typeclass laws. * @category harness */ export interface BuildParameterized<Typeclass extends TypeLambda> { <F extends TypeLambda, A, B = A, C = A, R = never, O = unknown, E = unknown>(given: ParameterizedGiven<Typeclass, F, A, B, C, R, O, E>, suffix?: string): LawSet; } export {}; //# sourceMappingURL=given.d.ts.map