/** * @since 0.24.0 */ import { dual } from "effect/Function" import type { TypeLambda } from "effect/HKT" import type { Order } from "effect/Order" import { map, reduce } from "./internal/Iterable.js" import type * as invariant from "./Invariant.js" import * as product_ from "./Product.js" import type * as semiProduct from "./SemiProduct.js" /** * @category type class * @since 0.24.0 */ export interface Semigroup { readonly combine: (self: A, that: A) => A readonly combineMany: (self: A, collection: Iterable) => A } /** * @category type lambdas * @since 0.24.0 */ export interface SemigroupTypeLambda extends TypeLambda { readonly type: Semigroup } /** * The `combineMany` parameter is optional and defaults to a standard * implementation. You can provide a custom implementation when performance * optimizations are possible. * * @category constructors * @since 0.24.0 */ export const make = ( combine: Semigroup["combine"], combineMany: Semigroup["combineMany"] = (self, collection) => reduce(self, combine)(collection) ): Semigroup => ({ combine, combineMany }) /** * `Semigroup` that returns last minimum of elements. * * @category constructors * @since 0.24.0 */ export const min = (O: Order): Semigroup => make((self, that) => O(self, that) === -1 ? self : that) /** * `Semigroup` that returns last maximum of elements. * * @category constructors * @since 0.24.0 */ export const max = (O: Order): Semigroup => make((self, that) => O(self, that) === 1 ? self : that) /** * @category constructors * @since 0.24.0 */ export const constant = (a: A): Semigroup => make(() => a, () => a) /** * The dual of a `Semigroup`, obtained by flipping the arguments of `combine`. * * @since 0.24.0 */ export const reverse = (S: Semigroup): Semigroup => make( (self, that) => S.combine(that, self), (self, collection) => { const reversed = Array.from(collection).reverse() return reversed.length > 0 ? S.combine(S.combineMany(reversed[0], reversed.slice(1)), self) : self } ) /** * The `intercalate` API returns a function that takes a `Semigroup` instance and a separator value, and returns a new * `Semigroup` instance that combines values with the given separator. * * This API is useful when you want to combine values with a specific separator. For example, when you want to concatenate * an array of strings with a separator string in between. * * It is interesting to note that there is no equivalent API in the `Monoid` module. This is because the value `empty`, * which is required for the `Monoid` interface, cannot exist. * * @since 0.24.0 */ export const intercalate: { /** * The `intercalate` API returns a function that takes a `Semigroup` instance and a separator value, and returns a new * `Semigroup` instance that combines values with the given separator. * * This API is useful when you want to combine values with a specific separator. For example, when you want to concatenate * an array of strings with a separator string in between. * * It is interesting to note that there is no equivalent API in the `Monoid` module. This is because the value `empty`, * which is required for the `Monoid` interface, cannot exist. * * @since 0.24.0 */ (separator: A): (S: Semigroup) => Semigroup /** * The `intercalate` API returns a function that takes a `Semigroup` instance and a separator value, and returns a new * `Semigroup` instance that combines values with the given separator. * * This API is useful when you want to combine values with a specific separator. For example, when you want to concatenate * an array of strings with a separator string in between. * * It is interesting to note that there is no equivalent API in the `Monoid` module. This is because the value `empty`, * which is required for the `Monoid` interface, cannot exist. * * @since 0.24.0 */ (S: Semigroup, separator: A): Semigroup } = dual( 2, (S: Semigroup, separator: A): Semigroup => make((self, that) => S.combineMany(self, [separator, that])) ) /** * Always return the first argument. * * @category instances * @since 0.24.0 */ export const first = (): Semigroup => make((a) => a, (a) => a) /** * Always return the last argument. * * @category instances * @since 0.24.0 */ export const last = (): Semigroup => make( (_, second) => second, (self, collection) => { let a: A = self // eslint-disable-next-line no-empty for (a of collection) {} return a } ) /** * @since 0.24.0 */ export const imap: { /** * @since 0.24.0 */ (to: (a: A) => B, from: (b: B) => A): (self: Semigroup) => Semigroup /** * @since 0.24.0 */ (self: Semigroup, to: (a: A) => B, from: (b: B) => A): Semigroup } = dual(3, (S: Semigroup, to: (a: A) => B, from: (b: B) => A): Semigroup => make( (self, that) => to(S.combine(from(self), from(that))), (self, collection) => to(S.combineMany(from(self), map(from)(collection))) )) /** * @category instances * @since 0.24.0 */ export const Invariant: invariant.Invariant = { imap } const product = (self: Semigroup, that: Semigroup): Semigroup<[A, B]> => make(([xa, xb], [ya, yb]) => [self.combine(xa, ya), that.combine(xb, yb)]) const productAll = (collection: Iterable>): Semigroup> => { return make((x, y) => { const len = Math.min(x.length, y.length) const out: Array = [] let collectionLength = 0 for (const s of collection) { if (collectionLength >= len) { break } out.push(s.combine(x[collectionLength], y[collectionLength])) collectionLength++ } return out }) } const productMany = ( self: Semigroup, collection: Iterable> ): Semigroup<[A, ...Array]> => { const semigroup = productAll(collection) return make((x, y) => [self.combine(x[0], y[0]), ...semigroup.combine(x.slice(1), y.slice(1))]) } /** * @category instances * @since 0.24.0 */ export const SemiProduct: semiProduct.SemiProduct = { imap, product, productMany } const of: (a: A) => Semigroup = constant /** * @category instances * @since 0.24.0 */ export const Product: product_.Product = { of, imap, product, productMany, productAll } /** * Similar to `Promise.all` but operates on `Semigroup`s. * * ``` * [Semigroup, Semigroup, ...] -> Semigroup<[A, B, ...]> * ``` * * This function creates and returns a new `Semigroup` for a tuple of values based on the given `Semigroup`s for each element in the tuple. * The returned `Semigroup` combines two tuples of the same type by applying the corresponding `Semigroup` passed as arguments to each element in the tuple. * * It is useful when you need to combine two tuples of the same type and you have a specific way of combining each element of the tuple. * * @category combinators * @since 0.24.0 */ export const tuple: >>( ...elements: T ) => Semigroup<{ readonly [I in keyof T]: [T[I]] extends [Semigroup] ? A : never }> = product_.tuple(Product) /** * Given a type `A`, this function creates and returns a `Semigroup` for `ReadonlyArray`. * The returned `Semigroup` combines two arrays by concatenating them. * * @category combinators * @since 0.24.0 */ export const array = (): Semigroup> => make((self, that) => self.concat(that)) /** * This function creates and returns a new `Semigroup` for a struct of values based on the given `Semigroup`s for each property in the struct. * The returned `Semigroup` combines two structs of the same type by applying the corresponding `Semigroup` passed as arguments to each property in the struct. * * It is useful when you need to combine two structs of the same type and you have a specific way of combining each property of the struct. * * @category combinators * @since 0.24.0 */ export const struct: }>( fields: R ) => Semigroup<{ readonly [K in keyof R]: [R[K]] extends [Semigroup] ? A : never }> = product_.struct(Product)