All files / src/laws/typeclass/concrete Isomorphism.ts

100% Statements 62/62
100% Branches 6/6
100% Functions 2/2
100% Lines 62/62

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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 1261x 1x 1x 1x     1x           1x 1x 1x   1x 1x 2x 2x 2x 2x 2x 2x 2x   1x 1x           1x 2x 2x 2x 2x   2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x   2x 2x 2x 2x 2x 200x 200x 200x 200x 200x 200x 2x   2x 2x 2x 2x 2x 200x 200x 200x 200x 200x 200x 2x 2x 2x                                                                                              
import {inverse} from '#algebra'
import {Law, LawSet} from '#law'
import {Isomorphism} from '#typeclass'
import {pipe} from 'effect'
import {type Equivalence} from 'effect/Equivalence'
import fc from 'fast-check'
import {defineConcreteLaws} from './given.js'
 
/**
 * Build typeclass laws for isomorphisms that share their `A` type parameter.
 * @category typeclass laws
 */
export const buildIsomorphismLaws =
  <A>(base: BaseIsomorphismGiven<A>) =>
  <Encodings extends BaseEncoding<A>>(encodings: Encodings) => {
    type Entry<K extends keyof Encodings> = [K & string, Encodings[K]]
    const results: LawSet[] = []
    for (const entry of Object.entries(encodings)) {
      const [suffix, entryGiven] = entry as Entry<keyof Encodings>
      const given: IsomorphismGivenFor<A, any> & BaseIsomorphismGiven<A> = {
        ...base,
        ...entryGiven,
      }
      results.push(isomorphismLaws(suffix, given))
    }
 
    return results
  }
 
/**
 * Build typeclass laws for `Isomorphism`.
 * @category typeclass laws
 */
export const isomorphismLaws = <A, B>(
  suffix: string,
  {a, b, equalsA, equalsB, F}: IsomorphismGiven<A, B>,
): LawSet => {
  const [encode, decode] = [Isomorphism.encode(F), Isomorphism.decode(F)]
 
  return defineConcreteLaws(
    'Isomorphism',
    inverse<A, B>(
      {f: encode, g: decode, a, equals: equalsA},
      'F.encode ⚬ F.decode = id',
      'decode/encode identity',
    ),
    inverse<B, A>(
      {f: decode, g: encode, a: b, equals: equalsB},
      'F.decode ⚬ F.encode = id',
      'encode/decode identity',
    ),
 
    Law(
      'reverse compose encode identity',
      'compose(F, reverse(F)).encode = id',
      a,
    )(a =>
      equalsA(
        Isomorphism.encode(
          pipe(F, Isomorphism.reverse, Isomorphism.compose(F)),
        )(a),
        a,
      ),
    ),
 
    Law(
      'reverse compose decode identity',
      'compose(F, reverse(F)).decode = id',
      a,
    )(a =>
      equalsA(
        Isomorphism.decode(
          pipe(F, Isomorphism.reverse, Isomorphism.compose(F)),
        )(a),
        a,
      ),
    ),
  )(suffix)
}
 
declare module './given.js' {
  interface ConcreteLambdas {
    Isomorphism: Isomorphism.IsomorphismTypeLambda
  }
}
 
export type IsomorphismGiven<A, B> = IsomorphismGivenFor<A, B> &
  BaseIsomorphismGiven<A>
 
/**
 * Record of arguments required to test several encodings of the type `A`.  Each
 * `Isomorphism` requires an equivalence and an arbitrary for the encoded type,
 * where the key is the encoding name. As all the encodings share a decoded
 * type, only a single arbitrary/equivalence of `A` is required.
 * @category typeclass laws
 */
export type BaseEncoding<A> = Record<string, IsomorphismGivenFor<A, any>>
 
/**
 * Arguments required to test a single `Isomorphism<A,B>`.
 * @category typeclass laws
 */
export interface IsomorphismGivenFor<A, B> {
  /** An equivalence for the decoded values of the isomorphism. */
  equalsB: Equivalence<B>
 
  /** An arbitrary for the decoded values of the isomorphism. */
  b: fc.Arbitrary<B>
 
  /** Instance of the typeclass under test. */
  F: Isomorphism.Isomorphism<A, B>
}
 
/**
 * Shared arguments required for testing an `Isomorphism` with a decoded type
 * `A`.
 * @category typeclass laws
 */
export interface BaseIsomorphismGiven<A> {
  /** An equivalence for the decoded value of the `Isomorphism` of type `A`. */
  equalsA: Equivalence<A>
 
  /** An arbitrary for the decoded value of the `Isomorphism` of type `A`. */
  a: fc.Arbitrary<A>
}