All files / src/laws/typeclass build.ts

100% Statements 29/29
100% Branches 8/8
100% Functions 3/3
100% Lines 29/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 901x       1x         1x                                                                         1x                   15x 15x 15x             15x 15x 15x 15x 15x 15x 15x 15x 15x   15x   15x 15x 9x 9x 9x 9x 6x 15x 14x 1x 15x 15x  
import {Array as AR, pipe, Tuple as TU} from 'effect'
import type {Kind, TypeLambda} from 'effect/HKT'
import {LawSet} from '../../law.js'
import type {Concrete, ConcreteClass} from './concrete/catalog.js'
import {buildConcreteTypeclassLaws} from './concrete/catalog.js'
import type {
  Parameterized,
  ParameterizedClass,
} from './parameterized/catalog.js'
import {
  buildParameterizedTypeclassLaws,
  isParameterizedTypeclassName,
} from './parameterized/catalog.js'
import type {GivenConcerns} from './parameterized/given.js'
 
/**
 * Union of all typeclass names.
 * @category model
 */
export type Typeclass = ParameterizedClass | ConcreteClass
 
/**
 * Some subset of all typeclass instances implemented for a single data
 * type. Can include both typeclasses for _parameterized_ and _concrete_
 * types.
 *
 * The keys in this object are the typeclass names, and the values are
 * the instances under test, where each value is the instance of the
 * typeclass for the corresponding key, and all are instances of their
 * typeclasses for the single data type under test.
 * @category harness
 */
export type TypeclassInstances<
  F extends TypeLambda,
  A,
  R = never,
  O = unknown,
  E = unknown,
> = Partial<Concrete<Kind<F, R, O, E, A>> & Parameterized<F>>
 
/**
 * Build typeclass laws for the given instances of some datatype.
 * Any instances of typeclasses with laws can be tested, concrete or
 * parameterized.
 * @category harness
 */
export const buildTypeclassLawsFor = <
  F extends TypeLambda,
  Ins extends TypeclassInstances<F, A, R, O, E>,
  A,
  B = A,
  C = A,
  R = never,
  O = unknown,
  E = unknown,
>(
  instances: Ins,
  given: GivenConcerns<F, A, B, C, R, O, E>,
): LawSet[] => {
  type ConcreteA = Kind<F, R, O, E, A>
 
  type Entry = {
    [K in keyof Ins]: [K & Typeclass, Ins[K]]
  }[keyof Ins]
 
  const [concrete, parameterized] = pipe(
    Object.entries(instances) as Entry[],
    AR.partition(([typeclass]) => isParameterizedTypeclassName(typeclass)),
    TU.mapBoth({
      onFirst: entries =>
        Object.fromEntries(entries) as Partial<Concrete<ConcreteA>>,
      onSecond: entries => Object.fromEntries(entries),
    }),
  )
 
  const {getEquivalence, equalsA, getArbitrary, a} = given
 
  return [
    ...(Object.keys(concrete).length !== 0
      ? buildConcreteTypeclassLaws(concrete, {
          a: getArbitrary(a),
          equalsA: getEquivalence(equalsA),
        })
      : []),
    ...(Object.keys(parameterized).length !== 0
      ? buildParameterizedTypeclassLaws<F, A, B, C>()(parameterized, given)
      : []),
  ]
}