Skip to content

Commit aa5ea03

Browse files
committed
Revise and expand README documentation
Improves the README with clearer library description, feature highlights, installation instructions, and expanded usage examples. Adds sections on integration with normalization libraries, performance tips, API overview, and licensing. Enhances clarity and guidance for new users.
1 parent 1e6b0f6 commit aa5ea03

1 file changed

Lines changed: 62 additions & 35 deletions

File tree

README.md

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,36 @@
44
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/cmp_gleam/)
55
[![CI](https://github.com/lupodevelop/cmp/workflows/test/badge.svg)](https://github.com/lupodevelop/cmp/actions)
66

7-
cmp_gleam: explicit comparator helpers for Gleam
7+
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.
88

9-
cmp_gleam is 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.
9+
## Features
1010

11-
Key goals:
11+
- ✨ Small and predictable API
12+
- 🔧 Composable building blocks for lexicographic and tie-breaker ordering
13+
- 🎯 Optional normalization/folding integration (e.g., with [`str`](https://github.com/lupodevelop/str)) without runtime dependencies
14+
- 🔒 Type-safe and total functions (no panics, no unsafe code)
1215

13-
- Keep the API small and predictable
14-
- Provide composable building blocks for lexicographic and tie-breaker ordering
15-
- Allow optional normalization/folding (for example using the `str` library) without adding runtime dependencies
16+
> **Note**: [`str` documentation](https://hexdocs.pm/str/) is available on HexDocs.
1617
17-
Why this approach?
18+
## Why this approach?
1819

19-
`cmp` 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.
20+
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.
2021

21-
Install
22+
## Installation
2223

2324
```sh
2425
gleam add cmp_gleam
2526
```
2627

27-
Quick examples 🔧
28+
Then import the `cmp` module in your code:
2829

29-
1. Sort integers
30+
```gleam
31+
import cmp
32+
```
33+
34+
## Quick examples
35+
36+
### 1. Sort integers
3037

3138
```gleam
3239
import cmp
@@ -37,7 +44,7 @@ pub fn sort_ints(xs: List(Int)) -> List(Int) {
3744
}
3845
```
3946

40-
2. Sort records by a string field (contramap)
47+
### 2. Sort records by a field (contramap pattern)
4148

4249
```gleam
4350
import cmp
@@ -54,58 +61,65 @@ pub fn sort_by_name(users: List(User)) -> List(User) {
5461
}
5562
```
5663

57-
3. Lexicographic ordering / tie-breakers (chain / then / lazy_then)
64+
### 3. Lexicographic ordering (chain / then / lazy_then)
65+
66+
Combine multiple comparators to sort by primary key, then by secondary key:
5867

5968
```gleam
60-
let cmp = cmp.chain([
69+
let comparator = cmp.chain([
6170
cmp.by(fn(u) { case u { User(name, _) -> name } }, string.compare),
6271
cmp.by(fn(u) { case u { User(_, age) -> age } }, cmp.natural_int)
6372
])
64-
list.sort(users, by: cmp)
73+
list.sort(users, by: comparator)
6574
```
6675

67-
4. Integrating with `str` for normalization (e.g. ASCII-fold)
76+
## Integration with `str` (optional)github.com/lupodevelop/str), but you can pass [`str`](https://github.com/lupodevelop/str
6877

69-
`str` is optional — `cmp` does not import it. You can pass `str` functions to `cmp` APIs such as `by_normalized_string` to fold/normalize before comparing:
78+
The library does **not** depend on [`str`](https://hexdocs.pm/str/), but you can pass [`str`](https://hexdocs.pm/str/) functions to the APIs for Unicode normalization and folding.
79+
80+
### ASCII folding example
7081

7182
```gleam
7283
import cmp
7384
import gleam/list
7485
import gleam/string
75-
import str.extra
86+
import str/extra
7687
7788
pub fn sort_by_name_ascii_fold(users: List(User)) -> List(User) {
7889
let normalize = str.extra.ascii_fold
79-
let cmp_name = cmp.by_normalized_string(fn(u) { case u { User(name, _) -> name } }, normalize, string.compare)
80-
list.sort(users, by: cmp_name)
90+
let comparator = cmp.by_normalized_string(
91+
fn(u) { case u { User(name, _) -> name } },
92+
normalize,
93+
string.compare
94+
)
95+
list.sort(users, by: comparator)
8196
}
8297
```
8398

84-
You can also combine normalization steps (for example: fold + lowercase):
99+
You can also combine normalization steps:
85100

86101
```gleam
87102
let normalize = fn(s) { s |> str.extra.ascii_fold |> string.lowercase }
88103
```
89104

90-
5. Using metrics (similarity/distance) to order items (advanced example)
105+
### Using similarity metrics (advanced)
91106

92107
⚠️ **Warning**: similarity/distance metrics don't guarantee transitivity (a < b and b < c doesn't always imply a < c). If you need a total order, sort by the metric value itself rather than using it directly as a comparator.
93108

109+
**Not recommended** (violates transitivity):
110+
94111
```gleam
95112
import gleam/order
96113
97-
// Example: sort by similarity to a reference string (careful: not a total order!)
98114
let similarity_cmp = fn(a, b) {
99115
let s = str.similarity(a, b)
100116
case s > 0.8 { True -> order.Lt False -> order.Gt }
101117
}
102-
let cmp_sim = cmp.by(fn(u) { case u { User(name, _) -> name } }, similarity_cmp)
103118
```
104119

105-
Better approach for metrics:
120+
**Better approach** (compute metric, then sort by it):
106121

107122
```gleam
108-
// Compute similarity as Float, then sort by that value
109123
let reference = "Alice"
110124
let with_similarity = list.map(users, fn(u) {
111125
let sim = str.similarity(u.name, reference)
@@ -114,37 +128,50 @@ let with_similarity = list.map(users, fn(u) {
114128
let sorted = list.sort(with_similarity, by: cmp.by(fn(pair) { pair.1 }, float.compare))
115129
```
116130

117-
Unicode & normalization notes ⚠️
131+
## Unicode & normalization notes
118132

119133
- `string.compare` compares strings as-is; composed vs decomposed characters may behave differently if not normalized.
120134
- For user-facing sorting (names, titles), it is recommended to **normalize** (NFC/NFD) or apply folding (remove accents) before comparing.
121-
- `str` provides useful primitives (`str.extra.ascii_fold`, `str.core.normalize_whitespace`, etc.) which you can pass directly to `cmp`.
135+
- [`str`](https://github.com/lupodevelop/str) provides useful primitives (`str.extra.ascii_fold`, `str.core.normalize_whitespace`, etc.) which you can pass directly to `cmp` functions.
122136

123-
Performance tip: for large lists, precompute normalized keys
137+
### Performance tip: precompute normalized keys
124138

125139
When sorting large lists by normalized strings, calling `normalize` on every comparison is expensive. Use the decorate-sort-undecorate pattern:
126140

127141
```gleam
128142
import gleam/list
129143
130-
// Precompute normalized keys once
144+
// 1. Decorate: precompute normalized keys once
131145
let decorated = list.map(users, fn(u) {
132146
let normalized_name = str.extra.ascii_fold(u.name)
133147
#(u, normalized_name)
134148
})
135149
136-
// Sort by the precomputed key
150+
// 2. Sort by the precomputed key
137151
let sorted_decorated = list.sort(decorated, by: cmp.by(fn(pair) { pair.1 }, string.compare))
138152
139-
// Extract the original values
153+
// 3. Undecorate: extract the original values
140154
let sorted_users = list.map(sorted_decorated, fn(pair) { pair.0 })
141155
```
142156

143-
Further documentation and examples will be published on <https://hexdocs.pm/cmp_gleam>.
157+
## API overview
158+
159+
The library exports a single module `cmp` with the following main functions:
160+
161+
- **Basic comparators**: `natural_int`, `natural_string`, `natural_float`
162+
- **Contramap helpers**: `by`, `by_int`, `by_string`, `by_float`, `by_string_with`, `by_normalized_string`
163+
- **Composition**: `then`, `chain`, `lazy_then`, `reverse`
164+
- **Containers**: `option`, `list_compare`, `pair`, `triple`
165+
166+
See the [full API documentation](https://hexdocs.pm/cmp_gleam/) for details.
144167

145168
## Development
146169

147170
```sh
148-
gleam run # Run the project
149171
gleam test # Run the tests
150-
```
172+
gleam build # Build the project
173+
```
174+
175+
## License
176+
177+
MIT License - see [LICENSE](LICENSE) file for details.

0 commit comments

Comments
 (0)