You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
8
8
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
10
10
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)
12
15
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.
16
17
17
-
Why this approach?
18
+
## Why this approach?
18
19
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.
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:
58
67
59
68
```gleam
60
-
let cmp = cmp.chain([
69
+
let comparator = cmp.chain([
61
70
cmp.by(fn(u) { case u { User(name, _) -> name } }, string.compare),
62
71
cmp.by(fn(u) { case u { User(_, age) -> age } }, cmp.natural_int)
63
72
])
64
-
list.sort(users, by: cmp)
73
+
list.sort(users, by: comparator)
65
74
```
66
75
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
68
77
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.
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)
81
96
}
82
97
```
83
98
84
-
You can also combine normalization steps (for example: fold + lowercase):
99
+
You can also combine normalization steps:
85
100
86
101
```gleam
87
102
let normalize = fn(s) { s |> str.extra.ascii_fold |> string.lowercase }
88
103
```
89
104
90
-
5. Using metrics (similarity/distance) to order items (advanced example)
105
+
###Using similarity metrics (advanced)
91
106
92
107
⚠️ **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.
93
108
109
+
**Not recommended** (violates transitivity):
110
+
94
111
```gleam
95
112
import gleam/order
96
113
97
-
// Example: sort by similarity to a reference string (careful: not a total order!)
98
114
let similarity_cmp = fn(a, b) {
99
115
let s = str.similarity(a, b)
100
116
case s > 0.8 { True -> order.Lt False -> order.Gt }
101
117
}
102
-
let cmp_sim = cmp.by(fn(u) { case u { User(name, _) -> name } }, similarity_cmp)
103
118
```
104
119
105
-
Better approach for metrics:
120
+
**Better approach** (compute metric, then sort by it):
106
121
107
122
```gleam
108
-
// Compute similarity as Float, then sort by that value
109
123
let reference = "Alice"
110
124
let with_similarity = list.map(users, fn(u) {
111
125
let sim = str.similarity(u.name, reference)
@@ -114,37 +128,50 @@ let with_similarity = list.map(users, fn(u) {
114
128
let sorted = list.sort(with_similarity, by: cmp.by(fn(pair) { pair.1 }, float.compare))
115
129
```
116
130
117
-
Unicode & normalization notes ⚠️
131
+
## Unicode & normalization notes
118
132
119
133
-`string.compare` compares strings as-is; composed vs decomposed characters may behave differently if not normalized.
120
134
- 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.
122
136
123
-
Performance tip: for large lists, precompute normalized keys
137
+
### Performance tip: precompute normalized keys
124
138
125
139
When sorting large lists by normalized strings, calling `normalize` on every comparison is expensive. Use the decorate-sort-undecorate pattern:
126
140
127
141
```gleam
128
142
import gleam/list
129
143
130
-
// Precompute normalized keys once
144
+
// 1. Decorate: precompute normalized keys once
131
145
let decorated = list.map(users, fn(u) {
132
146
let normalized_name = str.extra.ascii_fold(u.name)
133
147
#(u, normalized_name)
134
148
})
135
149
136
-
// Sort by the precomputed key
150
+
// 2. Sort by the precomputed key
137
151
let sorted_decorated = list.sort(decorated, by: cmp.by(fn(pair) { pair.1 }, string.compare))
138
152
139
-
// Extract the original values
153
+
// 3. Undecorate: extract the original values
140
154
let sorted_users = list.map(sorted_decorated, fn(pair) { pair.0 })
141
155
```
142
156
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:
0 commit comments