cmp_gleam
A tiny, focused library for building small, composable comparators in Gleam. It intentionally avoids magic (no derives, no hidden behavior) so that comparison logic remains explicit, easy to test and easy to reason about.
Features
- โจ Small and predictable API
- ๐ง Composable building blocks for lexicographic and tie-breaker ordering
- ๐ฏ Support for custom normalization/folding functions
- ๐ Type-safe and total functions (no panics, no unsafe code)
Why this approach?
The library favors explicitness over implicit derivation. By making comparators first-class and composable you get predictable behaviour, easier testing, and straightforward integration with normalization libraries when you need to handle real-world Unicode data.
Installation
gleam add cmp_gleam
Then import the cmp module in your code:
import cmp
Quick examples
1. Sort integers
import cmp
import gleam/list
pub fn sort_ints(xs: List(Int)) -> List(Int) {
list.sort(xs, by: cmp.natural_int)
}
2. Sort records by a field (contramap pattern)
import cmp
import gleam/list
import gleam/string
type User {
User(name: String, age: Int)
}
pub fn sort_by_name(users: List(User)) -> List(User) {
let cmp_name = cmp.by(fn(u) { case u { User(name, _) -> name } }, string.compare)
list.sort(users, by: cmp_name)
}
3. Lexicographic ordering (chain / then / lazy_then)
Combine multiple comparators to sort by primary key, then by secondary key:
let comparator = cmp.chain([
cmp.by(fn(u) { case u { User(name, _) -> name } }, string.compare),
cmp.by(fn(u) { case u { User(_, age) -> age } }, cmp.natural_int)
])
list.sort(users, by: comparator)
Unicode & normalization notes
string.comparecompares strings as-is; composed vs decomposed characters may behave differently if not normalized.- For user-facing sorting (names, titles), you may want to normalize (NFC/NFD) or apply folding (remove accents) before comparing.
- Use
by_normalized_stringorby_string_withto apply custom normalization functions. - This library has been tested with
strfor Unicode normalization and ASCII folding (e.g.,str.extra.ascii_fold).
Performance tip: precompute normalized keys
When sorting large lists by normalized strings, calling normalize on every comparison is expensive. Use the decorate-sort-undecorate pattern:
import gleam/list
// 1. Decorate: precompute normalized keys once
let decorated = list.map(users, fn(u) {
let normalized_name = your_normalize_fn(u.name)
#(u, normalized_name)
})
// 2. Sort by the precomputed key
let sorted_decorated = list.sort(decorated, by: cmp.by(fn(pair) { pair.1 }, string.compare))
// 3. Undecorate: extract the original values
let sorted_users = list.map(sorted_decorated, fn(pair) { pair.0 })
API overview
The library exports a single module cmp with the following main functions:
- Basic comparators:
natural_int,natural_string,natural_float - Contramap helpers:
by,by_int,by_string,by_float,by_string_with,by_normalized_string - Composition:
then,chain,lazy_then,reverse - Containers:
option,list_compare,pair,triple
See the full API documentation for details.
Development
gleam test # Run the tests
gleam build # Build the project
License
MIT License - see LICENSE file for details.