/** * @since 0.24.0 */ import { dual } from "effect/Function" import type { Kind, TypeLambda } from "effect/HKT" import type { Covariant } from "./Covariant.js" import type { Invariant } from "./Invariant.js" import type { SemiApplicative } from "./SemiApplicative.js" /** * @category type class * @since 0.24.0 */ export interface SemiProduct extends Invariant { readonly product: ( self: Kind, that: Kind ) => Kind readonly productMany: ( self: Kind, collection: Iterable> ) => Kind]> } /** * Returns a default `productMany` implementation. * * @category constructors * @since 0.24.0 */ export const productMany = ( map: Covariant["map"], product: SemiProduct["product"] ): SemiProduct["productMany"] => ( self: Kind, collection: Iterable> ) => { let out = map(self, (a): [A, ...Array] => [a]) for (const fa of collection) { out = map( product(out, fa), ([[head, ...tail], a]): [A, ...Array] => [head, ...tail, a] ) } return out } /** * Returns a default `product` composition. * * @since 0.24.0 */ export const productComposition = ( F: SemiApplicative, G: SemiProduct ) => ( self: Kind>, that: Kind> ): Kind< F, FR1 & FR2, FO1 | FO2, FE1 | FE2, Kind > => F.map(F.product(self, that), ([ga, gb]) => G.product(ga, gb)) /** * Returns a default `productMany` composition. * * @since 0.24.0 */ export const productManyComposition = ( F: SemiApplicative, G: SemiProduct ) => ( self: Kind>, collection: Iterable>> ): Kind]>> => F.map(F.productMany(self, collection), ([ga, ...gas]) => G.productMany(ga, gas)) /** * @category do notation * @since 0.24.0 */ export const andThenBind = (F: SemiProduct): { ( name: Exclude, that: Kind ): ( self: Kind ) => Kind ( self: Kind, name: Exclude, that: Kind ): Kind } => dual(3, ( self: Kind, name: Exclude, that: Kind ): Kind => F.imap( F.product(self, that), ([a, b]) => Object.assign({}, a, { [name]: b }) as any, ({ [name]: b, ...rest }) => [rest, b] as any )) /** * Appends an element to the end of a tuple. * * @since 0.24.0 */ export const appendElement = (F: SemiProduct): { ( that: Kind ): >( self: Kind ) => Kind , R2, O2, E2, B>( self: Kind, that: Kind ): Kind } => dual(2, , R2, O2, E2, B>( self: Kind, that: Kind ): Kind => F.imap(F.product(self, that), ([a, b]) => [...a, b], (ab) => [ab.slice(0, -1), ab[ab.length - 1]] as any)) /** * @since 0.24.0 */ export const nonEmptyTuple = (F: SemiProduct) => , ...Array>]>( ...elements: T ): Kind< F, ([T[number]] extends [Kind] ? R : never), ([T[number]] extends [Kind] ? O : never), ([T[number]] extends [Kind] ? E : never), { [I in keyof T]: [T[I]] extends [Kind] ? A : never } > => F.productMany(elements[0], elements.slice(1)) as any type EnforceNonEmptyRecord = keyof R extends never ? never : R /** * @since 0.24.0 */ export const nonEmptyStruct = (F: SemiProduct) => }>( fields: EnforceNonEmptyRecord & { readonly [x: string]: Kind } ): Kind< F, ([R[keyof R]] extends [Kind] ? R : never), ([R[keyof R]] extends [Kind] ? O : never), ([R[keyof R]] extends [Kind] ? E : never), { [K in keyof R]: [R[K]] extends [Kind] ? A : never } > => { const keys = Object.keys(fields) return F.imap( F.productMany(fields[keys[0]], keys.slice(1).map((k) => fields[k])), ([value, ...values]) => { const out: any = { [keys[0]]: value } for (let i = 0; i < values.length; i++) { out[keys[i + 1]] = values[i] } return out }, (r) => keys.map((k) => r[k]) as any ) }