mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-25 22:21:49 +02:00
* fix: initial checkin of v2 Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: slowly migrate IO Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: migrate MonadTraverseArray and TraverseArray Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: migrate traversal Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: complete migration of IO Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: migrate ioeither Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: refactorY Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: next step in migration Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: adjust IO generation code Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: get rid of more IO methods Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: get rid of more IO * fix: convert iooption Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: convert reader Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: convert a bit of reader Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: new build script Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: cleanup Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: reformat Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: simplify Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: some cleanup Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: adjust Pair to Haskell semantic Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: documentation and testcases Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: some performance optimizations Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: remove coverage Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: better doc Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> --------- Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
390 lines
9.7 KiB
Go
390 lines
9.7 KiB
Go
// Copyright (c) 2023 - 2025 IBM Corp.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/*
|
|
Package ord provides the Ord type class for types that support total ordering.
|
|
|
|
# Overview
|
|
|
|
An Ord represents a total ordering on a type. It extends Eq (equality) with
|
|
a comparison function that returns -1, 0, or 1 to indicate less than, equal to,
|
|
or greater than relationships.
|
|
|
|
The Ord interface:
|
|
|
|
type Ord[T any] interface {
|
|
Eq[T] // Provides Equals(x, y T) bool
|
|
Compare(x, y T) int // Returns -1, 0, or 1
|
|
}
|
|
|
|
Ord laws:
|
|
- Reflexivity: Compare(x, x) = 0
|
|
- Antisymmetry: if Compare(x, y) <= 0 and Compare(y, x) <= 0 then x = y
|
|
- Transitivity: if Compare(x, y) <= 0 and Compare(y, z) <= 0 then Compare(x, z) <= 0
|
|
- Totality: Compare(x, y) <= 0 or Compare(y, x) <= 0
|
|
|
|
# Basic Usage
|
|
|
|
Creating an Ord for integers:
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
|
|
result := intOrd.Compare(5, 3) // 1 (5 > 3)
|
|
result := intOrd.Compare(3, 5) // -1 (3 < 5)
|
|
result := intOrd.Compare(5, 5) // 0 (5 == 5)
|
|
|
|
equal := intOrd.Equals(5, 5) // true
|
|
|
|
Creating a custom Ord:
|
|
|
|
type Person struct {
|
|
Name string
|
|
Age int
|
|
}
|
|
|
|
// Order by age
|
|
personOrd := ord.MakeOrd(
|
|
func(p1, p2 Person) int {
|
|
if p1.Age < p2.Age {
|
|
return -1
|
|
} else if p1.Age > p2.Age {
|
|
return 1
|
|
}
|
|
return 0
|
|
},
|
|
func(p1, p2 Person) bool {
|
|
return p1.Age == p2.Age
|
|
},
|
|
)
|
|
|
|
Creating Ord from compare function only:
|
|
|
|
stringOrd := ord.FromCompare(func(a, b string) int {
|
|
if a < b {
|
|
return -1
|
|
} else if a > b {
|
|
return 1
|
|
}
|
|
return 0
|
|
})
|
|
// Equals is automatically derived from Compare
|
|
|
|
# Comparison Functions
|
|
|
|
Lt - Less than:
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
isLessThan5 := ord.Lt(intOrd)(5)
|
|
|
|
result := isLessThan5(3) // true
|
|
result := isLessThan5(5) // false
|
|
result := isLessThan5(7) // false
|
|
|
|
Leq - Less than or equal:
|
|
|
|
isAtMost5 := ord.Leq(intOrd)(5)
|
|
|
|
result := isAtMost5(3) // true
|
|
result := isAtMost5(5) // true
|
|
result := isAtMost5(7) // false
|
|
|
|
Gt - Greater than:
|
|
|
|
isGreaterThan5 := ord.Gt(intOrd)(5)
|
|
|
|
result := isGreaterThan5(3) // false
|
|
result := isGreaterThan5(5) // false
|
|
result := isGreaterThan5(7) // true
|
|
|
|
Geq - Greater than or equal:
|
|
|
|
isAtLeast5 := ord.Geq(intOrd)(5)
|
|
|
|
result := isAtLeast5(3) // false
|
|
result := isAtLeast5(5) // true
|
|
result := isAtLeast5(7) // true
|
|
|
|
Between - Check if value is in range [low, high):
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
isBetween3And7 := ord.Between(intOrd)(3, 7)
|
|
|
|
result := isBetween3And7(2) // false
|
|
result := isBetween3And7(3) // true
|
|
result := isBetween3And7(5) // true
|
|
result := isBetween3And7(7) // false
|
|
result := isBetween3And7(8) // false
|
|
|
|
# Min and Max
|
|
|
|
Min - Get the minimum of two values:
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
min := ord.Min(intOrd)
|
|
|
|
result := min(5, 3) // 3
|
|
result := min(3, 5) // 3
|
|
result := min(5, 5) // 5 (first argument when equal)
|
|
|
|
Max - Get the maximum of two values:
|
|
|
|
max := ord.Max(intOrd)
|
|
|
|
result := max(5, 3) // 5
|
|
result := max(3, 5) // 5
|
|
result := max(5, 5) // 5 (first argument when equal)
|
|
|
|
Clamp - Restrict a value to a range:
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
clamp := ord.Clamp(intOrd)(0, 100)
|
|
|
|
result := clamp(-10) // 0 (clamped to minimum)
|
|
result := clamp(50) // 50 (within range)
|
|
result := clamp(150) // 100 (clamped to maximum)
|
|
|
|
# Transforming Ord
|
|
|
|
Reverse - Invert the ordering:
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
reversedOrd := ord.Reverse(intOrd)
|
|
|
|
result := intOrd.Compare(5, 3) // 1 (5 > 3)
|
|
result := reversedOrd.Compare(5, 3) // -1 (3 > 5 in reversed order)
|
|
|
|
min := ord.Min(reversedOrd)
|
|
result := min(5, 3) // 5 (max in original order)
|
|
|
|
Contramap - Transform the input before comparing:
|
|
|
|
type Person struct {
|
|
Name string
|
|
Age int
|
|
}
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
|
|
// Order persons by age
|
|
personOrd := ord.Contramap(func(p Person) int {
|
|
return p.Age
|
|
})(intOrd)
|
|
|
|
p1 := Person{Name: "Alice", Age: 30}
|
|
p2 := Person{Name: "Bob", Age: 25}
|
|
|
|
result := personOrd.Compare(p1, p2) // 1 (30 > 25)
|
|
|
|
ToEq - Convert Ord to Eq:
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
intEq := ord.ToEq(intOrd)
|
|
|
|
result := intEq.Equals(5, 5) // true
|
|
result := intEq.Equals(5, 3) // false
|
|
|
|
# Semigroup and Monoid
|
|
|
|
Semigroup - Combine orderings (try first, then second):
|
|
|
|
type Person struct {
|
|
LastName string
|
|
FirstName string
|
|
}
|
|
|
|
stringOrd := ord.FromStrictCompare[string]()
|
|
|
|
// Order by last name
|
|
byLastName := ord.Contramap(func(p Person) string {
|
|
return p.LastName
|
|
})(stringOrd)
|
|
|
|
// Order by first name
|
|
byFirstName := ord.Contramap(func(p Person) string {
|
|
return p.FirstName
|
|
})(stringOrd)
|
|
|
|
// Combine: order by last name, then first name
|
|
sg := ord.Semigroup[Person]()
|
|
personOrd := sg.Concat(byLastName, byFirstName)
|
|
|
|
p1 := Person{LastName: "Smith", FirstName: "Alice"}
|
|
p2 := Person{LastName: "Smith", FirstName: "Bob"}
|
|
|
|
result := personOrd.Compare(p1, p2) // -1 (Alice < Bob)
|
|
|
|
Monoid - Semigroup with identity (always equal):
|
|
|
|
m := ord.Monoid[int]()
|
|
|
|
// Empty ordering considers everything equal
|
|
emptyOrd := m.Empty()
|
|
result := emptyOrd.Compare(5, 3) // 0 (always equal)
|
|
|
|
// Concat with empty returns the original
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
result := m.Concat(intOrd, emptyOrd) // same as intOrd
|
|
|
|
MaxSemigroup - Semigroup that returns maximum:
|
|
|
|
intOrd := ord.FromStrictCompare[int]()
|
|
maxSg := ord.MaxSemigroup(intOrd)
|
|
|
|
result := maxSg.Concat(5, 3) // 5
|
|
result := maxSg.Concat(3, 5) // 5
|
|
|
|
MinSemigroup - Semigroup that returns minimum:
|
|
|
|
minSg := ord.MinSemigroup(intOrd)
|
|
|
|
result := minSg.Concat(5, 3) // 3
|
|
result := minSg.Concat(3, 5) // 3
|
|
|
|
# Practical Examples
|
|
|
|
Sorting with custom order:
|
|
|
|
import (
|
|
"sort"
|
|
O "github.com/IBM/fp-go/v2/ord"
|
|
)
|
|
|
|
type Person struct {
|
|
Name string
|
|
Age int
|
|
}
|
|
|
|
people := []Person{
|
|
{Name: "Alice", Age: 30},
|
|
{Name: "Bob", Age: 25},
|
|
{Name: "Charlie", Age: 35},
|
|
}
|
|
|
|
intOrd := O.FromStrictCompare[int]()
|
|
personOrd := O.Contramap(func(p Person) int {
|
|
return p.Age
|
|
})(intOrd)
|
|
|
|
sort.Slice(people, func(i, j int) bool {
|
|
return personOrd.Compare(people[i], people[j]) < 0
|
|
})
|
|
// people is now sorted by age
|
|
|
|
Finding min/max in a slice:
|
|
|
|
import (
|
|
A "github.com/IBM/fp-go/v2/array"
|
|
O "github.com/IBM/fp-go/v2/ord"
|
|
)
|
|
|
|
numbers := []int{5, 2, 8, 1, 9, 3}
|
|
intOrd := O.FromStrictCompare[int]()
|
|
|
|
min := O.Min(intOrd)
|
|
max := O.Max(intOrd)
|
|
|
|
// Find minimum
|
|
minimum := A.Reduce(numbers, min, numbers[0]) // 1
|
|
|
|
// Find maximum
|
|
maximum := A.Reduce(numbers, max, numbers[0]) // 9
|
|
|
|
Multi-level sorting:
|
|
|
|
type Employee struct {
|
|
Department string
|
|
Name string
|
|
Salary int
|
|
}
|
|
|
|
stringOrd := O.FromStrictCompare[string]()
|
|
intOrd := O.FromStrictCompare[int]()
|
|
|
|
// Order by department
|
|
byDept := O.Contramap(func(e Employee) string {
|
|
return e.Department
|
|
})(stringOrd)
|
|
|
|
// Order by salary (descending)
|
|
bySalary := O.Reverse(O.Contramap(func(e Employee) int {
|
|
return e.Salary
|
|
})(intOrd))
|
|
|
|
// Order by name
|
|
byName := O.Contramap(func(e Employee) string {
|
|
return e.Name
|
|
})(stringOrd)
|
|
|
|
// Combine: dept, then salary (desc), then name
|
|
sg := O.Semigroup[Employee]()
|
|
employeeOrd := sg.Concat(sg.Concat(byDept, bySalary), byName)
|
|
|
|
Filtering with comparisons:
|
|
|
|
import (
|
|
A "github.com/IBM/fp-go/v2/array"
|
|
O "github.com/IBM/fp-go/v2/ord"
|
|
)
|
|
|
|
numbers := []int{1, 5, 3, 8, 2, 9, 4}
|
|
intOrd := O.FromStrictCompare[int]()
|
|
|
|
// Filter numbers greater than 5
|
|
gt5 := O.Gt(intOrd)(5)
|
|
result := A.Filter(gt5)(numbers) // [8, 9]
|
|
|
|
// Filter numbers between 3 and 7
|
|
between3And7 := O.Between(intOrd)(3, 7)
|
|
result := A.Filter(between3And7)(numbers) // [5, 3, 4]
|
|
|
|
# Functions
|
|
|
|
Core operations:
|
|
- MakeOrd[T any](func(T, T) int, func(T, T) bool) Ord[T] - Create Ord from compare and equals
|
|
- FromCompare[T any](func(T, T) int) Ord[T] - Create Ord from compare (derives equals)
|
|
- FromStrictCompare[A Ordered]() Ord[A] - Create Ord for built-in ordered types
|
|
- ToEq[T any](Ord[T]) Eq[T] - Convert Ord to Eq
|
|
|
|
Transformations:
|
|
- Reverse[T any](Ord[T]) Ord[T] - Invert the ordering
|
|
- Contramap[A, B any](func(B) A) func(Ord[A]) Ord[B] - Transform input before comparing
|
|
|
|
Comparisons:
|
|
- Lt[A any](Ord[A]) func(A) func(A) bool - Less than
|
|
- Leq[A any](Ord[A]) func(A) func(A) bool - Less than or equal
|
|
- Gt[A any](Ord[A]) func(A) func(A) bool - Greater than
|
|
- Geq[A any](Ord[A]) func(A) func(A) bool - Greater than or equal
|
|
- Between[A any](Ord[A]) func(A, A) func(A) bool - Check if in range [low, high)
|
|
|
|
Min/Max/Clamp:
|
|
- Min[A any](Ord[A]) func(A, A) A - Get minimum of two values
|
|
- Max[A any](Ord[A]) func(A, A) A - Get maximum of two values
|
|
- Clamp[A any](Ord[A]) func(A, A) func(A) A - Clamp value to range
|
|
|
|
Algebraic structures:
|
|
- Semigroup[A any]() Semigroup[Ord[A]] - Combine orderings
|
|
- Monoid[A any]() Monoid[Ord[A]] - Semigroup with identity (always equal)
|
|
- MaxSemigroup[A any](Ord[A]) Semigroup[A] - Semigroup returning maximum
|
|
- MinSemigroup[A any](Ord[A]) Semigroup[A] - Semigroup returning minimum
|
|
|
|
# Related Packages
|
|
|
|
- eq: Equality type class (parent of Ord)
|
|
- constraints: Type constraints for generics
|
|
- semigroup: Associative binary operation
|
|
- monoid: Semigroup with identity element
|
|
*/
|
|
package ord
|