|
| 1 | +// Package fp provides functional programming utilities for Gno, enabling |
| 2 | +// transformations, filtering, and other operations on slices of interface{}. |
| 3 | +// |
| 4 | +// Example of chaining operations: |
| 5 | +// |
| 6 | +// numbers := []interface{}{1, 2, 3, 4, 5, 6} |
| 7 | +// |
| 8 | +// // Define predicates, mappers and reducers |
| 9 | +// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } |
| 10 | +// double := func(v interface{}) interface{} { return v.(int) * 2 } |
| 11 | +// sum := func(a, b interface{}) interface{} { return a.(int) + b.(int) } |
| 12 | +// |
| 13 | +// // Chain operations: filter even numbers, double them, then sum |
| 14 | +// evenNums := Filter(numbers, isEven) // [2, 4, 6] |
| 15 | +// doubled := Map(evenNums, double) // [4, 8, 12] |
| 16 | +// result := Reduce(doubled, sum, 0) // 24 |
| 17 | +// |
| 18 | +// // Alternative: group by even/odd, then get even numbers |
| 19 | +// byMod2 := func(v interface{}) interface{} { return v.(int) % 2 } |
| 20 | +// grouped := GroupBy(numbers, byMod2) // {0: [2,4,6], 1: [1,3,5]} |
| 21 | +// evens := grouped[0] // [2,4,6] |
| 22 | +package fp |
| 23 | + |
| 24 | +// Mapper is a function type that maps an element to another element. |
| 25 | +type Mapper func(interface{}) interface{} |
| 26 | + |
| 27 | +// Predicate is a function type that evaluates a condition on an element. |
| 28 | +type Predicate func(interface{}) bool |
| 29 | + |
| 30 | +// Reducer is a function type that reduces two elements to a single value. |
| 31 | +type Reducer func(interface{}, interface{}) interface{} |
| 32 | + |
| 33 | +// Filter filters elements from the slice that satisfy the given predicate. |
| 34 | +// |
| 35 | +// Example: |
| 36 | +// |
| 37 | +// numbers := []interface{}{-1, 0, 1, 2} |
| 38 | +// isPositive := func(v interface{}) bool { return v.(int) > 0 } |
| 39 | +// result := Filter(numbers, isPositive) // [1, 2] |
| 40 | +func Filter(values []interface{}, fn Predicate) []interface{} { |
| 41 | + result := []interface{}{} |
| 42 | + for _, v := range values { |
| 43 | + if fn(v) { |
| 44 | + result = append(result, v) |
| 45 | + } |
| 46 | + } |
| 47 | + return result |
| 48 | +} |
| 49 | + |
| 50 | +// Map applies a function to each element in the slice. |
| 51 | +// |
| 52 | +// Example: |
| 53 | +// |
| 54 | +// numbers := []interface{}{1, 2, 3} |
| 55 | +// toString := func(v interface{}) interface{} { return fmt.Sprintf("%d", v) } |
| 56 | +// result := Map(numbers, toString) // ["1", "2", "3"] |
| 57 | +func Map(values []interface{}, fn Mapper) []interface{} { |
| 58 | + result := make([]interface{}, len(values)) |
| 59 | + for i, v := range values { |
| 60 | + result[i] = fn(v) |
| 61 | + } |
| 62 | + return result |
| 63 | +} |
| 64 | + |
| 65 | +// Reduce reduces a slice to a single value by applying a function. |
| 66 | +// |
| 67 | +// Example: |
| 68 | +// |
| 69 | +// numbers := []interface{}{1, 2, 3, 4} |
| 70 | +// sum := func(a, b interface{}) interface{} { return a.(int) + b.(int) } |
| 71 | +// result := Reduce(numbers, sum, 0) // 10 |
| 72 | +func Reduce(values []interface{}, fn Reducer, initial interface{}) interface{} { |
| 73 | + acc := initial |
| 74 | + for _, v := range values { |
| 75 | + acc = fn(acc, v) |
| 76 | + } |
| 77 | + return acc |
| 78 | +} |
| 79 | + |
| 80 | +// FlatMap maps each element to a collection and flattens the results. |
| 81 | +// |
| 82 | +// Example: |
| 83 | +// |
| 84 | +// words := []interface{}{"hello", "world"} |
| 85 | +// split := func(v interface{}) interface{} { |
| 86 | +// chars := []interface{}{} |
| 87 | +// for _, c := range v.(string) { |
| 88 | +// chars = append(chars, string(c)) |
| 89 | +// } |
| 90 | +// return chars |
| 91 | +// } |
| 92 | +// result := FlatMap(words, split) // ["h","e","l","l","o","w","o","r","l","d"] |
| 93 | +func FlatMap(values []interface{}, fn Mapper) []interface{} { |
| 94 | + result := []interface{}{} |
| 95 | + for _, v := range values { |
| 96 | + inner := fn(v).([]interface{}) |
| 97 | + result = append(result, inner...) |
| 98 | + } |
| 99 | + return result |
| 100 | +} |
| 101 | + |
| 102 | +// All returns true if all elements satisfy the predicate. |
| 103 | +// |
| 104 | +// Example: |
| 105 | +// |
| 106 | +// numbers := []interface{}{2, 4, 6, 8} |
| 107 | +// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } |
| 108 | +// result := All(numbers, isEven) // true |
| 109 | +func All(values []interface{}, fn Predicate) bool { |
| 110 | + for _, v := range values { |
| 111 | + if !fn(v) { |
| 112 | + return false |
| 113 | + } |
| 114 | + } |
| 115 | + return true |
| 116 | +} |
| 117 | + |
| 118 | +// Any returns true if at least one element satisfies the predicate. |
| 119 | +// |
| 120 | +// Example: |
| 121 | +// |
| 122 | +// numbers := []interface{}{1, 3, 4, 7} |
| 123 | +// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } |
| 124 | +// result := Any(numbers, isEven) // true (4 is even) |
| 125 | +func Any(values []interface{}, fn Predicate) bool { |
| 126 | + for _, v := range values { |
| 127 | + if fn(v) { |
| 128 | + return true |
| 129 | + } |
| 130 | + } |
| 131 | + return false |
| 132 | +} |
| 133 | + |
| 134 | +// None returns true if no elements satisfy the predicate. |
| 135 | +// |
| 136 | +// Example: |
| 137 | +// |
| 138 | +// numbers := []interface{}{1, 3, 5, 7} |
| 139 | +// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } |
| 140 | +// result := None(numbers, isEven) // true (no even numbers) |
| 141 | +func None(values []interface{}, fn Predicate) bool { |
| 142 | + for _, v := range values { |
| 143 | + if fn(v) { |
| 144 | + return false |
| 145 | + } |
| 146 | + } |
| 147 | + return true |
| 148 | +} |
| 149 | + |
| 150 | +// Chunk splits a slice into chunks of the given size. |
| 151 | +// |
| 152 | +// Example: |
| 153 | +// |
| 154 | +// numbers := []interface{}{1, 2, 3, 4, 5} |
| 155 | +// result := Chunk(numbers, 2) // [[1,2], [3,4], [5]] |
| 156 | +func Chunk(values []interface{}, size int) [][]interface{} { |
| 157 | + if size <= 0 { |
| 158 | + return nil |
| 159 | + } |
| 160 | + var chunks [][]interface{} |
| 161 | + for i := 0; i < len(values); i += size { |
| 162 | + end := i + size |
| 163 | + if end > len(values) { |
| 164 | + end = len(values) |
| 165 | + } |
| 166 | + chunks = append(chunks, values[i:end]) |
| 167 | + } |
| 168 | + return chunks |
| 169 | +} |
| 170 | + |
| 171 | +// Find returns the first element that satisfies the predicate and a boolean indicating if an element was found. |
| 172 | +// |
| 173 | +// Example: |
| 174 | +// |
| 175 | +// numbers := []interface{}{1, 2, 3, 4} |
| 176 | +// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } |
| 177 | +// result, found := Find(numbers, isEven) // 2, true |
| 178 | +func Find(values []interface{}, fn Predicate) (interface{}, bool) { |
| 179 | + for _, v := range values { |
| 180 | + if fn(v) { |
| 181 | + return v, true |
| 182 | + } |
| 183 | + } |
| 184 | + return nil, false |
| 185 | +} |
| 186 | + |
| 187 | +// Reverse reverses the order of elements in a slice. |
| 188 | +// |
| 189 | +// Example: |
| 190 | +// |
| 191 | +// numbers := []interface{}{1, 2, 3} |
| 192 | +// result := Reverse(numbers) // [3, 2, 1] |
| 193 | +func Reverse(values []interface{}) []interface{} { |
| 194 | + result := make([]interface{}, len(values)) |
| 195 | + for i, v := range values { |
| 196 | + result[len(values)-1-i] = v |
| 197 | + } |
| 198 | + return result |
| 199 | +} |
| 200 | + |
| 201 | +// Zip combines two slices into a slice of pairs. If the slices have different lengths, |
| 202 | +// extra elements from the longer slice are ignored. |
| 203 | +// |
| 204 | +// Example: |
| 205 | +// |
| 206 | +// a := []interface{}{1, 2, 3} |
| 207 | +// b := []interface{}{"a", "b", "c"} |
| 208 | +// result := Zip(a, b) // [[1,"a"], [2,"b"], [3,"c"]] |
| 209 | +func Zip(a, b []interface{}) [][2]interface{} { |
| 210 | + length := min(len(a), len(b)) |
| 211 | + result := make([][2]interface{}, length) |
| 212 | + for i := 0; i < length; i++ { |
| 213 | + result[i] = [2]interface{}{a[i], b[i]} |
| 214 | + } |
| 215 | + return result |
| 216 | +} |
| 217 | + |
| 218 | +// Unzip splits a slice of pairs into two separate slices. |
| 219 | +// |
| 220 | +// Example: |
| 221 | +// |
| 222 | +// pairs := [][2]interface{}{{1,"a"}, {2,"b"}, {3,"c"}} |
| 223 | +// numbers, letters := Unzip(pairs) // [1,2,3], ["a","b","c"] |
| 224 | +func Unzip(pairs [][2]interface{}) ([]interface{}, []interface{}) { |
| 225 | + a := make([]interface{}, len(pairs)) |
| 226 | + b := make([]interface{}, len(pairs)) |
| 227 | + for i, pair := range pairs { |
| 228 | + a[i] = pair[0] |
| 229 | + b[i] = pair[1] |
| 230 | + } |
| 231 | + return a, b |
| 232 | +} |
| 233 | + |
| 234 | +// GroupBy groups elements based on a key returned by a Mapper. |
| 235 | +// |
| 236 | +// Example: |
| 237 | +// |
| 238 | +// numbers := []interface{}{1, 2, 3, 4, 5, 6} |
| 239 | +// byMod3 := func(v interface{}) interface{} { return v.(int) % 3 } |
| 240 | +// result := GroupBy(numbers, byMod3) // {0: [3,6], 1: [1,4], 2: [2,5]} |
| 241 | +func GroupBy(values []interface{}, fn Mapper) map[interface{}][]interface{} { |
| 242 | + result := make(map[interface{}][]interface{}) |
| 243 | + for _, v := range values { |
| 244 | + key := fn(v) |
| 245 | + result[key] = append(result[key], v) |
| 246 | + } |
| 247 | + return result |
| 248 | +} |
| 249 | + |
| 250 | +// Flatten flattens a slice of slices into a single slice. |
| 251 | +// |
| 252 | +// Example: |
| 253 | +// |
| 254 | +// nested := [][]interface{}{{1,2}, {3,4}, {5}} |
| 255 | +// result := Flatten(nested) // [1,2,3,4,5] |
| 256 | +func Flatten(values [][]interface{}) []interface{} { |
| 257 | + result := []interface{}{} |
| 258 | + for _, v := range values { |
| 259 | + result = append(result, v...) |
| 260 | + } |
| 261 | + return result |
| 262 | +} |
| 263 | + |
| 264 | +// Helper functions |
| 265 | +func min(a, b int) int { |
| 266 | + if a < b { |
| 267 | + return a |
| 268 | + } |
| 269 | + return b |
| 270 | +} |
0 commit comments