// ets_tracing: off import * as A from "../../Collections/Immutable/Array/index.js" import * as NEA from "../../Collections/Immutable/NonEmptyArray/index.js" import * as T from "../../Effect/index.js" import * as E from "../../Either/index.js" import { flow, identity, pipe } from "../../Function/index.js" import * as O from "../../Option/index.js" import * as ST from "../../Structural/index.js" import * as PR from "../Primitives/index.js" export const BoolAlgebraTypeId = Symbol() export const ValueTypeId = Symbol() export const AndTypeId = Symbol() export const OrTypeId = Symbol() export const NotTypeId = Symbol() export function concrete(_: BoolAlgebra): asserts _ is typeof _[typeof PR._C] { // } /** * A `BoolAlgebra` is a description of logical operations on values of type * `A`. */ export abstract class BoolAlgebra implements ST.HasEquals { readonly [BoolAlgebraTypeId]: typeof BoolAlgebraTypeId = BoolAlgebraTypeId; readonly [PR._A]: () => A; readonly [PR._C]: Value | And | Or | Not; abstract [ST.equalsSym](that: unknown): boolean get [ST.hashSym](): number { return fold_( this, (a) => ST.hash(a), (a, b) => a & b, (a, b) => a | b, (a) => ~a ) } ["&&"](that: BoolAlgebra): BoolAlgebra { return and_(this, that) } ["||"](that: BoolAlgebra): BoolAlgebra { return or_(this, that) } get ["!"](): BoolAlgebra { return not(this) } } export class Value extends BoolAlgebra { readonly typeId: typeof ValueTypeId = ValueTypeId constructor(readonly value: A) { super() } [ST.equalsSym](that: unknown): boolean { if (isBoolAlgebra(that)) { return this.equal(that) || doubleNegative(this, that) } return false } get [ST.hashSym](): number { return fold_( this, (a) => ST.hash(a), (a, b) => a & b, (a, b) => a | b, (a) => ~a ) } private equal(that: BoolAlgebra): boolean { if (isValue(that)) { return ST.equals(this.value, that.value) } return false } } export function isValue(a: BoolAlgebra): a is Value { concrete(a) return a.typeId === ValueTypeId } export class And extends BoolAlgebra { readonly typeId: typeof AndTypeId = AndTypeId constructor(readonly left: BoolAlgebra, readonly right: BoolAlgebra) { super() } [ST.equalsSym](that: unknown): boolean { if (isBoolAlgebra(that)) { return ( this.equal(that) || this.commutative(that) || symmetric(And.associative)(this, that) || symmetric(And.distributive)(this, that) || doubleNegative(this, that) || this.deMorgansLaws(that) ) } return false } private equal(that: BoolAlgebra): boolean { if (isAnd(that)) { return ST.equals(this.left, that.left) && ST.equals(this.right, that.right) } return false } private static associative(left: BoolAlgebra, right: BoolAlgebra): boolean { if (isAnd(left) && isAnd(right)) { if (isAnd(left.left) && isAnd(right.right)) { const { left: a1, right: b1 } = left.left const c1 = left.right const { left: b2, right: c2 } = right.right const a2 = right.left return ST.equals(a1, a2) && ST.equals(b1, b2) && ST.equals(c1, c2) } } return false } private commutative(that: BoolAlgebra): boolean { if (isAnd(that)) { const { left: al, right: bl } = this const { left: ar, right: br } = that return ST.equals(al, br) && ST.equals(bl, ar) } return false } private static distributive( left: BoolAlgebra, right: BoolAlgebra ): boolean { if (isAnd(left) && isOr(right)) { if (isOr(left.right) && isAnd(right.left) && isAnd(right.right)) { const a1 = left.left const { left: b1, right: c1 } = left.right const { left: a2, right: b2 } = right.left const { left: a3, right: c2 } = right.right return ( ST.equals(a1, a2) && ST.equals(a1, a3) && ST.equals(b1, b2) && ST.equals(c1, c2) ) } } return false } private deMorgansLaws(that: BoolAlgebra): boolean { if (isNot(that)) { if (isNot(this.left) && isNot(this.right)) { if (isOr(that.result)) { const a = this.left.result const b = this.right.result const { left: c, right: d } = that.result return ST.equals(a, c) && ST.equals(b, d) } } } return false } } export function isAnd(a: BoolAlgebra): a is And { concrete(a) return a.typeId === AndTypeId } export class Or extends BoolAlgebra { readonly typeId: typeof OrTypeId = OrTypeId constructor(readonly left: BoolAlgebra, readonly right: BoolAlgebra) { super() } [ST.equalsSym](that: unknown): boolean { if (isBoolAlgebra(that)) { return ( this.equal(that) || this.commutative(that) || symmetric(Or.associative)(this, that) || symmetric(Or.distributive)(this, that) || doubleNegative(this, that) || this.deMorgansLaws(that) ) } return false } private equal(that: BoolAlgebra): boolean { if (isOr(that)) { return ST.equals(this.left, that.left) && ST.equals(this.right, that.right) } return false } private static associative(left: BoolAlgebra, right: BoolAlgebra): boolean { if (isOr(left) && isOr(left.left)) { if (isOr(right) && isOr(right.right)) { const { left: a1, right: b1 } = left.left const c1 = left.right const a2 = right.left const { left: b2, right: c2 } = right.right return ST.equals(a1, a2) && ST.equals(b1, b2) && ST.equals(c1, c2) } } return false } private commutative(that: BoolAlgebra): boolean { if (isOr(that)) { const { left: al, right: bl } = this const { left: ar, right: br } = that return ST.equals(al, br) && ST.equals(bl, ar) } return false } private static distributive( left: BoolAlgebra, right: BoolAlgebra ): boolean { if (isOr(left) && isAnd(left.right)) { if (isAnd(right) && isOr(right.left) && isOr(right.right)) { const a1 = left.left const { left: b1, right: c1 } = left.right const { left: a2, right: b2 } = right.left const { left: a3, right: c2 } = right.right return ( ST.equals(a1, a2) && ST.equals(a1, a3) && ST.equals(b1, b2) && ST.equals(c1, c2) ) } } return false } private deMorgansLaws(that: BoolAlgebra): boolean { if (isNot(this.left) && isNot(this.right)) { if (isNot(that) && isAnd(that.result)) { const a = this.left.result const b = this.right.result const { left: c, right: d } = that.result return ST.equals(a, c) && ST.equals(b, d) } } return false } } export function isOr(a: BoolAlgebra): a is Or { concrete(a) return a.typeId === OrTypeId } export class Not extends BoolAlgebra { readonly typeId: typeof NotTypeId = NotTypeId constructor(readonly result: BoolAlgebra) { super() } [ST.equalsSym](that: unknown): boolean { if (isBoolAlgebra(that)) { return this.equal(that) || doubleNegative(that, this) || this.deMorgansLaws(that) } return false } private equal(that: BoolAlgebra): boolean { if (isNot(that)) { return ST.equals(this.result, that.result) } return false } private deMorgansLaws(that: BoolAlgebra): boolean { if (isAnd(that)) { if (isOr(this.result) && isNot(that.left) && isNot(that.right)) { const { left: a, right: b } = this.result const c = that.left.result const d = that.right.result return ST.equals(a, c) && ST.equals(b, d) } } if (isOr(that)) { if (isAnd(this.result) && isNot(that.left) && isNot(that.right)) { const { left: a, right: b } = this.result const c = that.left.result const d = that.right.result return ST.equals(a, c) && ST.equals(b, d) } } return false } } export function isNot(a: BoolAlgebra): a is Not { concrete(a) return a.typeId === NotTypeId } export function isBoolAlgebra(a: unknown): a is BoolAlgebra { return typeof a === "object" && a !== null && BoolAlgebraTypeId in a } function doubleNegative(left: BoolAlgebra, right: BoolAlgebra): boolean { if (isNot(right) && isNot(right.result)) { return ST.equals(left, right.result.result) } return false } function symmetric>( f: (a1: A, a2: A) => boolean ): (a1: A, a2: A) => boolean { return (a1, a2) => f(a1, a2) || f(a2, a1) } /** * Returns a new result, with all values mapped to the specified constant. */ export function as_(self: BoolAlgebra, b: B): BoolAlgebra { return map_(self, (_) => b) } /** * Returns a new result, with all values mapped to the specified constant. */ export function as(b: B) { return (self: BoolAlgebra) => as_(self, b) } /** * If this result is a success returns `None`. If it is a failure returns a * new result containing all failures that are relevant to this result being * a failure. */ export function failures(self: BoolAlgebra): O.Option> { return pipe( fold_, BoolAlgebra>>( self, (a) => E.right(success(a)), (l, r) => { if (E.isRight(l)) { if (E.isRight(r)) { return E.right(and_(l.right, r.right)) } else { return E.left(r.left) } } else { if (E.isRight(r)) { return E.left(l.left) } else { return E.left(and_(l.left, r.left)) } } }, (l, r) => { if (E.isRight(l)) { if (E.isRight(r)) { return E.right(or_(l.right, r.right)) } else { return E.right(l.right) } } else { if (E.isRight(r)) { return E.right(r.right) } else { return E.left(or_(l.left, r.left)) } } }, (r) => E.swap(r) ), E.fold( (_) => O.some(_), (_) => O.none ) ) } /** * Returns a new result, with all values mapped to new results using the * specified function. */ export function chain_( self: BoolAlgebra, f: (a: A) => BoolAlgebra ): BoolAlgebra { return fold_(self, f, and_, or_, not) } /** * Returns a new result, with all values mapped to new results using the * specified function. */ export function chain(f: (a: A) => BoolAlgebra) { return (self: BoolAlgebra) => chain_(self, f) } /** * Returns a new result, with all values mapped to new results using the * specified effectual function. */ export function chainM_( self: BoolAlgebra, f: (a: A) => T.Effect> ): T.Effect> { return fold_( self, f, (_) => T.zipWith_(_, _, and_), (_) => T.zipWith_(_, _, or_), (_) => T.map_(_, not) ) } /** * Returns a new result, with all values mapped to new results using the * specified effectual function. */ export function chainM(f: (a: A) => T.Effect>) { return (self: BoolAlgebra) => chainM_(self, f) } /** * Folds over the result bottom up, first converting values to `B` * values, and then combining the `B` values, using the specified functions. */ export function fold_( self: BoolAlgebra, caseValue: (a: A) => B, caseAnd: (b1: B, b2: B) => B, caseOr: (b1: B, b2: B) => B, caseNot: (b: B) => B ): B { concrete(self) switch (self.typeId) { case ValueTypeId: return caseValue(self.value) case AndTypeId: return caseAnd( fold_(self.left, caseValue, caseAnd, caseOr, caseNot), fold_(self.right, caseValue, caseAnd, caseOr, caseNot) ) case OrTypeId: return caseOr( fold_(self.left, caseValue, caseAnd, caseOr, caseNot), fold_(self.right, caseValue, caseAnd, caseOr, caseNot) ) case NotTypeId: return caseNot(fold_(self.result, caseValue, caseAnd, caseOr, caseNot)) } } /** * Folds over the result bottom up, first converting values to `B` * values, and then combining the `B` values, using the specified functions. */ export function fold( caseValue: (a: A) => B, caseAnd: (b1: B, b2: B) => B, caseOr: (b1: B, b2: B) => B, caseNot: (b: B) => B ) { return (self: BoolAlgebra) => fold_(self, caseValue, caseAnd, caseOr, caseNot) } export function implies_( self: BoolAlgebra, that: BoolAlgebra ): BoolAlgebra { return or_(not(self), that) } export function implies(that: BoolAlgebra) { return (self: BoolAlgebra) => implies_(self, that) } export function iff_(self: BoolAlgebra, that: BoolAlgebra): BoolAlgebra { return and_(implies_(self, that), implies_(that, self)) } export function iff(that: BoolAlgebra) { return (self: BoolAlgebra) => iff_(self, that) } /** * Determines whether the result is a failure, where values represent success * and are combined using logical conjunction, disjunction, and negation. */ export function isFailure(self: BoolAlgebra): boolean { return !isSuccess(self) } /** * Determines whether the result is a success, where values represent success * and are combined using logical conjunction, disjunction, and negation. */ export function isSuccess(self: BoolAlgebra): boolean { return fold_( self, (_): boolean => true, (a, b) => a && b, (a, b) => a || b, (a) => !a ) } /** * Returns a new result, with all values mapped by the specified function. */ export function map_(self: BoolAlgebra, f: (a: A) => B): BoolAlgebra { return chain_(self, flow(f, success)) } /** * Returns a new result, with all values mapped by the specified function. */ export function map(f: (a: A) => B) { return (self: BoolAlgebra) => map_(self, f) } /** * Returns a new result, with all values mapped by the specified effectual * function. */ export function mapM_( self: BoolAlgebra, f: (a: A) => T.Effect ): T.Effect> { return chainM_(self, (a) => T.map_(f(a), success)) } /** * Returns a new result, with all values mapped by the specified effectual * function. */ export function mapM(f: (a: A) => T.Effect) { return (self: BoolAlgebra) => mapM_(self, f) } /** * Returns a result that is the logical conjunction of all of the results in * the specified collection. */ export function all(as: Iterable>): O.Option> { const arr = A.from(as) if (A.isNonEmpty(arr)) { return O.some(A.reduce_(A.drop_(arr, 1), arr[0], and_)) } return O.none } /** * Constructs a result that is the logical conjunction of two results. */ export function and_( left: BoolAlgebra, right: BoolAlgebra ): BoolAlgebra { return new And(left, right) } /** * Constructs a result that is the logical conjunction of two results. */ export function and(right: BoolAlgebra) { return (left: BoolAlgebra) => and_(left, right) } /** * Returns a result that is the logical disjunction of all of the results in * the specified collection. */ export function any(as: Iterable>): O.Option> { const arr = A.from(as) if (A.isNonEmpty(arr)) { const [init, ...rest] = arr return O.some(A.reduce_(rest, init, or_)) } return O.none } /** * Combines a collection of results to create a single result that succeeds * if all of the results succeed. */ export function collectAll(as: Iterable>): O.Option> { return forEach(as, identity) } /** * Constructs a failed result with the specified value. */ export function failure(a: A): BoolAlgebra { return not(success(a)) } /** * Applies the function `f` to each element of the `Iterable[A]` to produce * a collection of results, then combines all of those results to create a * single result that is the logical conjunction of all of the results. */ export function forEach( as: Iterable, f: (a: A) => BoolAlgebra ): O.Option> { const arr = A.from(as) if (A.isNonEmpty(arr)) { const result = NEA.map_(arr, f) return O.some(A.reduce_(NEA.tail(result), NEA.head(result), and_)) } return O.none } /** * Constructs a result that is the logical negation of the specified result. */ export function not(result: BoolAlgebra): BoolAlgebra { return new Not(result) } /** * Constructs a result a that is the logical disjunction of two results. */ export function or_( left: BoolAlgebra, right: BoolAlgebra ): BoolAlgebra { return new Or(left, right) } /** * Constructs a result a that is the logical disjunction of two results. */ export function or(right: BoolAlgebra) { return (left: BoolAlgebra) => or_(left, right) } /** * Constructs a successful result with the specified value. */ export function success(a: A): BoolAlgebra { return new Value(a) } /** * A successful result with the unit value. */ export const unit: BoolAlgebra = success(undefined)