// 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)