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

100% Statements 22/22
100% Branches 6/6
100% Functions 9/9
100% Lines 22/22

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 131                                    45x       4x         45x 4x 4x 4x 4x       4x             301x           302x           400x             45x                     2x 2x   2x 2x 2x 2x   2x         200x                   200x                           200x                                          
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
  }
}