All files / src/laws/typeclass/parameterized Alternative.ts

100% Statements 78/78
100% Branches 12/12
100% Functions 3/3
100% Lines 78/78

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 126 127 128 129 130 1311x 1x         1x       1x   1x           1x 4x 4x   4x 4x 4x 4x   1x 4x 4x 4x 4x       4x 4x   4x 4x 4x 4x 4x   4x 4x 4x 4x 4x   4x 4x 4x 4x 4x   4x 4x 4x     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 2x   2x 2x 2x   2x 2x 2x 2x 200x 200x 200x 200x 2x 2x 2x                              
import {addLawSets, Law, lawTests} from '#law'
import {
  Alternative as AL,
  Applicative as AP,
  SemiApplicative as SE,
} from '@effect/typeclass'
import {pipe} from 'effect'
import type {TypeLambda} from 'effect/HKT'
import {UnderlyingArbitrary} from '../../../arbitrary.js'
import type {BuildParameterized, ParameterizedGiven as Given} from './given.js'
import {unfoldGiven} from './given.js'
import {BuildInternal} from './internal.js'
import {semiAlternativeLaws} from './SemiAlternative.js'
 
/**
 * Typeclass laws for `Alternative`.
 * @category typeclass laws
 */
export const alternativeLaws: BuildParameterized<AlternativeTypeLambda> = (
  given,
  suffix?,
) =>
  pipe(
    buildLaws(`Alternative${suffix ?? ''}`, given),
    pipe(given, semiAlternativeLaws, addLawSets),
  )
 
const buildLaws: BuildInternal<AlternativeTypeLambda> = (name, given) => {
  const unfolded = unfoldGiven(given),
    {F, equalsFa, fa} = unfolded,
    {coproduct, coproductAll} = F,
    isApplicative = 'productAll' in F && 'of' in F
 
  type A = UnderlyingArbitrary<(typeof unfolded)['a']>
 
  return lawTests(
    name,
 
    Law(
      'left identity',
      '∅ ⊕ fa = fa',
      fa,
    )(fa => equalsFa(coproduct(fa, F.zero<A>()), fa)),
 
    Law(
      'right identity',
      'fa ⊕ ∅ = fa',
      fa,
    )(fa => equalsFa(coproduct(F.zero<A>(), fa), fa)),
 
    Law(
      'coproductAll zero',
      'coproductAll([]) = ∅',
      fa,
    )(() => equalsFa(coproductAll([]), F.zero<A>())),
 
    ...(isApplicative ? buildApplicativeLaws(given) : []),
  )
}
 
// Extra laws for alternatives that have an applicative instance.
const buildApplicativeLaws = <
  F extends TypeLambda,
  A,
  B = A,
  C = A,
  R = never,
  O = unknown,
  E = unknown,
>(
  given: Given<AlternativeTypeLambda, F, A, B, C, R, O, E>,
) => {
  const {F, equalsFb, fa, ab, fabOf} = unfoldGiven(given),
    {coproduct, map} = F
 
  const Ap = F as unknown as AP.Applicative<F>,
    ap = SE.ap(Ap),
    of = Ap.of,
    fab = fabOf(of)
 
  return [
    Law(
      'right absorption',
      'ap(fab, ∅) = ∅',
      fab,
    )(fab => equalsFb(ap(fab, F.zero<A>()), F.zero<B>())),
 
    Law(
      'left distributivity',
      'coproduct(fa₁, fa₂) ▹ map(ab) =' +
        ' coproduct(map(fa₁, ab), map(fa₂, ab))',
      fa,
      fa,
      ab,
    )((fa1, fa2, ab) =>
      equalsFb(
        pipe(coproduct(fa1, fa2), map(ab)),
        coproduct(map(fa1, ab), map(fa2, ab)),
      ),
    ),
 
    Law(
      'right distributivity',
      'coproduct(fab₁, fab₂) ▹ ap(fa) =' +
        ' coproduct(ap(fab₁, fa), ap(fab₂, fa))',
      fa,
      fab,
      fab,
    )((fa, fab1, fab2) =>
      equalsFb(
        pipe(coproduct(fab1, fab2), ap(fa)),
        coproduct(ap(fab1, fa), ap(fab2, fa)),
      ),
    ),
  ]
}
 
/**
 * Type lambda for the `Alternative` typeclass.
 * @category type lambda
 */
export interface AlternativeTypeLambda extends TypeLambda {
  readonly type: AL.Alternative<this['Target'] & TypeLambda>
}
 
declare module './given.js' {
  interface ParameterizedLambdas {
    Alternative: AlternativeTypeLambda
  }
}