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.
- ✨ 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)
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.
gleam add cmp_gleamThen import the cmp module in your code:
importcmpimportcmpimportgleam/listpubfnsort_ints(xs:List(Int))->List(Int){list.sort(xs,by:cmp.natural_int)}importcmpimportgleam/listimportgleam/stringtypeUser{User(name:String,age:Int)}pubfnsort_by_name(users:List(User))->List(User){letcmp_name=cmp.by(fn(u){caseu{User(name,_)->name}},string.compare)list.sort(users,by:cmp_name)}Combine multiple comparators to sort by primary key, then by secondary key:
letcomparator=cmp.chain([cmp.by(fn(u){caseu{User(name,_)->name}},string.compare),cmp.by(fn(u){caseu{User(_,age)->age}},cmp.natural_int)])list.sort(users,by:comparator)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).
When sorting large lists by normalized strings, calling normalize on every comparison is expensive. Use the decorate-sort-undecorate pattern:
importgleam/list// 1. Decorate: precompute normalized keys onceletdecorated=list.map(users,fn(u){letnormalized_name=your_normalize_fn(u.name)#(u,normalized_name)})// 2. Sort by the precomputed keyletsorted_decorated=list.sort(decorated,by:cmp.by(fn(pair){pair.1},string.compare))// 3. Undecorate: extract the original valuesletsorted_users=list.map(sorted_decorated,fn(pair){pair.0})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.
gleam test# Run the tests
gleam build # Build the projectMIT License - see LICENSE file for details.