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