Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/Semigroup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, test } from "bun:test"

import { Product, Str, Sum } from "./Semigroup.ts"

describe("Semigroup", () => {
describe("Sum", () => {
test("should add values via concat", () => {
const a = new Sum(3)
const b = new Sum(5)
expect(a.concat(b).value).toBe(8)
})

test("should satisfy associativity law", () => {
const a = new Sum(1)
const b = new Sum(2)
const c = new Sum(3)
expect(a.concat(b).concat(c).value).toBe(a.concat(b.concat(c)).value)
})

test("should correctly render when apply toString()", () => {
const a = new Sum(42)
expect(a.toString()).toBe("Sum(42)")
})
})

describe("Product", () => {
test("should multiply values via concat", () => {
const a = new Product(3)
const b = new Product(5)
expect(a.concat(b).value).toBe(15)
})

test("should satisfy associativity law", () => {
const a = new Product(2)
const b = new Product(3)
const c = new Product(4)
expect(a.concat(b).concat(c).value).toBe(a.concat(b.concat(c)).value)
})

test("should correctly render when apply toString()", () => {
const a = new Product(7)
expect(a.toString()).toBe("Product(7)")
})
})

describe("Str", () => {
test("should concatenate strings via concat", () => {
const a = new Str("hello")
const b = new Str(" world")
expect(a.concat(b).value).toBe("hello world")
})

test("should satisfy associativity law", () => {
const a = new Str("foo")
const b = new Str("bar")
const c = new Str("baz")
expect(a.concat(b).concat(c).value).toBe(a.concat(b.concat(c)).value)
})

test("should correctly render when apply toString()", () => {
const a = new Str("hello")
expect(a.toString()).toBe("Str(hello)")
})
})
})
79 changes: 79 additions & 0 deletions src/Semigroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* A Semigroup is a type with an associative binary operation (`concat`).
* For any values a, b, c: a.concat(b).concat(c) === a.concat(b.concat(c))
*/
export interface Semigroup<T> {
concat(other: T): T
}

/**
* A Semigroup under addition.
* Wraps a number and combines by adding values together.
*/
export class Sum implements Semigroup<Sum> {
readonly #value: number

constructor(value: number) {
this.#value = value
}

get value(): number {
return this.#value
}

concat(other: Sum): Sum {
return new Sum(this.#value + other.value)
}

toString(): string {
return `Sum(${this.#value})`
}
}

/**
* A Semigroup under multiplication.
* Wraps a number and combines by multiplying values together.
*/
export class Product implements Semigroup<Product> {
readonly #value: number

constructor(value: number) {
this.#value = value
}

get value(): number {
return this.#value
}

concat(other: Product): Product {
return new Product(this.#value * other.value)
}

toString(): string {
return `Product(${this.#value})`
}
}

/**
* A Semigroup under string concatenation.
* Wraps a string and combines by concatenating values together.
*/
export class Str implements Semigroup<Str> {
readonly #value: string

constructor(value: string) {
this.#value = value
}

get value(): string {
return this.#value
}

concat(other: Str): Str {
return new Str(this.#value + other.value)
}

toString(): string {
return `Str(${this.#value})`
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./Monad.ts"
export * from "./Option.ts"
export * from "./Pipe.ts"
export * from "./Result.ts"
export * from "./Semigroup.ts"