export const ParSeqSym = Symbol.for("@tsplus/stdlib/collections/ParSeq") export type ParSeqSym = typeof ParSeqSym export const _A = Symbol.for("@tsplus/stdlib/collections/ParSeq/A") export type _A = typeof _A /** * `ParSeq` is a data type that represents some notion of "events" that can * take place in parallel or in sequence. For example, a `ParSeq` * parameterized on some error type could be used to model the potentially * multiple ways that an application can fail. On the other hand, a `ParSeq` * parameterized on some request type could be used to model a collection of * requests to external data sources, some of which could be executed in * parallel and some of which must be executed sequentially. * * @tsplus type ParSeq */ export type ParSeq = Empty | Single | Then | Both /** * @tsplus type ParSeq.Ops */ export interface ParSeqOps { $: ParSeqAspects } export const ParSeq: ParSeqOps = { $: {} } /** * @tsplus type ParSeq.Aspects */ export interface ParSeqAspects {} const _emptyHash = Hash.optimize(Hash.random()) /** * @tsplus type ParSeq/Empty */ export class Empty implements Equals { readonly _tag = "Empty" readonly [ParSeqSym]: ParSeqSym = ParSeqSym readonly [_A]!: () => never; [Equals.sym](that: unknown): boolean { return isParSeq(that) && this.equalsSafe(that).run } [Hash.sym](): number { return _emptyHash } equalsSafe(that: ParSeq): Eval { return Eval.succeed(that._tag === "Empty") } } /** * @tsplus type ParSeq/Single */ export class Single implements Equals { readonly _tag = "Single" readonly [ParSeqSym]: ParSeqSym = ParSeqSym readonly [_A]!: () => A constructor(readonly a: A) {} [Equals.sym](that: unknown): boolean { return isParSeq(that) && this.equalsSafe(that).run } [Hash.sym](): number { return Hash.combine(Hash.string(this._tag), Hash.unknown(this.a)) } equalsSafe(that: ParSeq): Eval { return Eval.succeed(that._tag === "Single" && Equals.equals(this.a, that.a)) } } /** * @tsplus type ParSeq/Then */ export class Then implements Equals { readonly _tag = "Then" readonly [ParSeqSym]: ParSeqSym = ParSeqSym readonly [_A]!: () => A constructor(readonly left: ParSeq, readonly right: ParSeq) {} [Equals.sym](that: unknown): boolean { return isParSeq(that) && this.equalsSafe(that).run } [Hash.sym](): number { return hashCode(this) } equalsSafe(that: ParSeq): Eval { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this return Eval.gen(function*($) { return ( (yield* $(self.eq(that))) || (yield* $(symmetric(associateThen)(self, that))) || (yield* $(symmetric(distributiveThen)(self, that))) || (yield* $(symmetric(zero)(self, that))) ) }) } private eq(that: ParSeq): Eval { if (that._tag === "Then") { return this.left.equalsSafe(that.left).zipWith( this.right.equalsSafe(that.right), (a, b) => a && b ) } return Eval.succeed(false) } } /** * @tsplus type ParSeq/Both */ export class Both implements Equals { readonly _tag = "Both" readonly [ParSeqSym]: ParSeqSym = ParSeqSym readonly [_A]!: () => A constructor(readonly left: ParSeq, readonly right: ParSeq) {} [Equals.sym](that: unknown): boolean { return isParSeq(that) && this.equalsSafe(that).run } [Hash.sym](): number { return hashCode(this) } equalsSafe(that: ParSeq): Eval { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this return Eval.gen(function*(_) { return ( (yield* _(self.eq(that))) || (yield* _(symmetric(associativeBoth)(self, that))) || (yield* _(symmetric(distributiveBoth)(self, that))) || (yield* _(commutativeBoth(self, that))) || (yield* _(symmetric(zero)(self, that))) ) }) } private eq(that: ParSeq): Eval { if (that._tag === "Both") { return this.left.equalsSafe(that.left).zipWith( this.right.equalsSafe(that.right), (a, b) => a && b ) } return Eval.succeed(false) } } function associateThen(self: ParSeq, that: ParSeq): Eval { if ( self._tag === "Then" && self.left._tag === "Then" && that._tag === "Then" && that.right._tag === "Then" ) { const al = self.left.left const bl = self.left.right const cl = self.right const ar = that.left const br = that.right.left const cr = that.right.right return al.equalsSafe(ar).zipWith( bl.equalsSafe(br).zipWith( cl.equalsSafe(cr), (a, b) => a && b ), (a, b) => a && b ) } return Eval.succeed(false) } function distributiveThen(self: ParSeq, that: ParSeq): Eval { return Eval.gen(function*(_) { if ( self._tag === "Then" && self.right._tag === "Both" && that._tag === "Both" && that.left._tag === "Then" && that.right._tag === "Then" ) { const al = self.left const bl = self.right.left const cl = self.right.right const ar1 = that.left.left const br = that.left.right const ar2 = that.right.left const cr = that.right.right if ( (yield* _(ar1.equalsSafe(ar2))) && (yield* _(al.equalsSafe(ar1))) && (yield* _(bl.equalsSafe(br))) && (yield* _(cl.equalsSafe(cr))) ) { return true } } if ( self._tag === "Then" && self.left._tag === "Both" && that._tag === "Both" && that.left._tag === "Then" && that.right._tag === "Then" ) { const al = self.left.left const bl = self.left.right const cl = self.right const ar = that.left.left const cr1 = that.left.right const br = that.right.left const cr2 = that.right.right if ( (yield* _(cr1.equalsSafe(cr2))) && (yield* _(al.equalsSafe(ar))) && (yield* _(bl.equalsSafe(br))) && (yield* _(cl.equalsSafe(cr1))) ) { return true } } return false }) } function associativeBoth(self: ParSeq, that: ParSeq): Eval { return Eval.gen(function*(_) { if ( self._tag === "Both" && self.left._tag === "Both" && that._tag === "Both" && that.right._tag === "Both" ) { const al = self.left.left const bl = self.left.right const cl = self.right const ar = that.left const br = that.right.left const cr = that.right.right return ( (yield* _(al.equalsSafe(ar))) && (yield* _(bl.equalsSafe(br))) && (yield* _(cl.equalsSafe(cr))) ) } return false }) } function distributiveBoth(self: ParSeq, that: ParSeq): Eval { return Eval.gen(function*(_) { if ( self._tag === "Both" && self.left._tag === "Then" && self.right._tag === "Then" && that._tag === "Then" && that.right._tag === "Both" ) { const al1 = self.left.left const bl = self.left.right const al2 = self.right.left const cl = self.right.right const ar = that.left const br = that.right.left const cr = that.right.right if ( (yield* _(al1.equalsSafe(al2))) && (yield* _(al1.equalsSafe(ar))) && (yield* _(bl.equalsSafe(br))) && (yield* _(cl.equalsSafe(cr))) ) { return true } } if ( self._tag === "Both" && self.left._tag === "Then" && self.right._tag === "Then" && that._tag === "Then" && that.left._tag === "Both" ) { const al = self.left.left const cl1 = self.left.right const bl = self.right.left const cl2 = self.right.right const ar = that.left.left const br = that.left.right const cr = that.right if ( (yield* _(cl1.equalsSafe(cl2))) && (yield* _(al.equalsSafe(ar))) && (yield* _(bl.equalsSafe(br))) && (yield* _(cl1.equalsSafe(cr))) ) { return true } } return false }) } function commutativeBoth(self: Both, that: ParSeq): Eval { if (that._tag === "Both") { return self.left.equalsSafe(that.right).zipWith( self.right.equalsSafe(that.left), (a, b) => a && b ) } return Eval.succeed(false) } function zero(self: ParSeq, that: ParSeq) { if (self._tag === "Then" && self.right._tag === "Empty") { return self.left.equalsSafe(that) } if (self._tag === "Then" && self.left._tag === "Empty") { return self.right.equalsSafe(that) } if (self._tag === "Both" && self.right._tag === "Empty") { return self.left.equalsSafe(that) } if (self._tag === "Both" && self.left._tag === "Empty") { return self.right.equalsSafe(that) } return Eval.succeed(false) } function symmetric(f: (a: ParSeq, b: ParSeq) => Eval) { return (a: ParSeq, b: ParSeq) => Eval.gen(function*(_) { return (yield* _(f(a, b))) || (yield* _(f(b, a))) }) } function hashCode(self: ParSeq): number { const flat = flatten(self) const size = flat.length let head if (size === 0) { return _emptyHash } else if (size === 1 && (head = flat.unsafeHead!) && head.size === 1) { return List.make(head).unsafeHead![Hash.sym]() } else { return Hash.iterator(flat[Symbol.iterator]()) } } /** * Empty collection of events * * @tsplus static ParSeq.Ops empty */ export function empty(): ParSeq { return new Empty() } /** * Constructs a new collection of events that contains the specified event. * * @tsplus static ParSeq.Ops single */ export function single(a: A): ParSeq { return new Single(a) } /** * Combines this collection of events with that collection of events to * return a new collection of events that represents this collection of * events in parallel with that collection of events. * * @tsplus static ParSeq.Ops combinePar */ export function combinePar_(left: ParSeq, right: ParSeq): ParSeq { return left.isEmpty ? right : right.isEmpty ? left : new Both(left, right) } /** * Combines this collection of events with that collection of events to * return a new collection of events that represents this collection of * events in parallel with that collection of events. * * @tsplus static ParSeq.Aspects combinePar */ export const combinePar = Pipeable(combinePar_) /** * Combines this collection of events with that collection of events to * return a new collection of events that represents this collection of * events followed by that collection of events. * * @tsplus static ParSeq.Ops combineSeq */ export function combineSeq_(left: ParSeq, right: ParSeq): ParSeq { return left.isEmpty ? right : right.isEmpty ? left : new Then(left, right) } /** * Combines this collection of events with that collection of events to * return a new collection of events that represents this collection of * events followed by that collection of events. * * @tsplus static ParSeq.Aspects combineSeq */ export const combineSeq = Pipeable(combineSeq_) /** * @tsplus static ParSeq.Ops isParSeq */ export function isParSeq(u: unknown): u is ParSeq { return typeof u === "object" && u != null && ParSeqSym in u } /** * Checks if the ParSeq is empty. * * @tsplus getter ParSeq isEmpty */ export function isEmpty(self: ParSeq): boolean { return isEmptyLoop(List.make(self)) } function isEmptyLoop(self: List>): boolean { while (!self.isNil()) { const head = self.head const tail = self.tail switch (head._tag) { case "Empty": { self = tail break } case "Single": { return false } case "Both": { self = tail.prepend(head.right).prepend(head.left) break } case "Then": { self = tail.prepend(head.right).prepend(head.left) break } } } return true } function flatten(self: ParSeq) { return flattenLoop(List(self), List.empty()) } function flattenLoop( causes: List>, flattened: List>> ): List>> { // eslint-disable-next-line no-constant-condition while (1) { const [parallel, sequential] = causes.reduce( [HashSet.empty>(), List.empty>()] as const, ([parallel, sequential], cause) => { const [set, seq] = step(cause) return [parallel.union(set), sequential + seq] } ) const updated = parallel.size > 0 ? flattened.prepend(parallel) : flattened if (sequential.isNil()) { return updated.reverse } else { causes = sequential flattened = updated } } throw new Error("Bug") } function step(self: ParSeq): readonly [HashSet>, List>] { return stepLoop(self, List.empty(), HashSet.empty(), List.empty()) } function stepLoop( cause: ParSeq, stack: List>, parallel: HashSet>, sequential: List> ): readonly [HashSet>, List>] { // eslint-disable-next-line no-constant-condition while (1) { switch (cause._tag) { case "Empty": { if (stack.isNil()) { return [parallel, sequential] } else { cause = stack.head! stack = stack.tail } break } case "Both": { stack = stack.prepend(cause.right) cause = cause.left break } case "Then": { const left = cause.left const right = cause.right switch (left._tag) { case "Empty": { cause = cause.right break } case "Then": { cause = combineSeq_(left.left, combineSeq_(left.right, right)) break } case "Both": { cause = combinePar_(combineSeq_(left.left, right), combineSeq_(left.right, right)) break } default: { cause = left sequential = sequential.prepend(right) } } break } default: { if (stack.isNil()) { return [parallel.add(cause), sequential] } else { parallel = parallel.add(cause) cause = stack.head stack = stack.tail break } } } } throw new Error("Bug") }