fp.gno
6.57 Kb ยท 270 lines
1// Package fp provides functional programming utilities for Gno, enabling
2// transformations, filtering, and other operations on slices of any.
3//
4// Example of chaining operations:
5//
6// numbers := []any{1, 2, 3, 4, 5, 6}
7//
8// // Define predicates, mappers and reducers
9// isEven := func(v any) bool { return v.(int)%2 == 0 }
10// double := func(v any) any { return v.(int) * 2 }
11// sum := func(a, b any) any { 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 any) any { return v.(int) % 2 }
20// grouped := GroupBy(numbers, byMod2) // {0: [2,4,6], 1: [1,3,5]}
21// evens := grouped[0] // [2,4,6]
22package fp
23
24// Mapper is a function type that maps an element to another element.
25type Mapper func(any) any
26
27// Predicate is a function type that evaluates a condition on an element.
28type Predicate func(any) bool
29
30// Reducer is a function type that reduces two elements to a single value.
31type Reducer func(any, any) any
32
33// Filter filters elements from the slice that satisfy the given predicate.
34//
35// Example:
36//
37// numbers := []any{-1, 0, 1, 2}
38// isPositive := func(v any) bool { return v.(int) > 0 }
39// result := Filter(numbers, isPositive) // [1, 2]
40func Filter(values []any, fn Predicate) []any {
41 result := []any{}
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 := []any{1, 2, 3}
55// toString := func(v any) any { return fmt.Sprintf("%d", v) }
56// result := Map(numbers, toString) // ["1", "2", "3"]
57func Map(values []any, fn Mapper) []any {
58 result := make([]any, 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 := []any{1, 2, 3, 4}
70// sum := func(a, b any) any { return a.(int) + b.(int) }
71// result := Reduce(numbers, sum, 0) // 10
72func Reduce(values []any, fn Reducer, initial any) any {
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 := []any{"hello", "world"}
85// split := func(v any) any {
86// chars := []any{}
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"]
93func FlatMap(values []any, fn Mapper) []any {
94 result := []any{}
95 for _, v := range values {
96 inner := fn(v).([]any)
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 := []any{2, 4, 6, 8}
107// isEven := func(v any) bool { return v.(int)%2 == 0 }
108// result := All(numbers, isEven) // true
109func All(values []any, 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 := []any{1, 3, 4, 7}
123// isEven := func(v any) bool { return v.(int)%2 == 0 }
124// result := Any(numbers, isEven) // true (4 is even)
125func Any(values []any, 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 := []any{1, 3, 5, 7}
139// isEven := func(v any) bool { return v.(int)%2 == 0 }
140// result := None(numbers, isEven) // true (no even numbers)
141func None(values []any, 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 := []any{1, 2, 3, 4, 5}
155// result := Chunk(numbers, 2) // [[1,2], [3,4], [5]]
156func Chunk(values []any, size int) [][]any {
157 if size <= 0 {
158 return nil
159 }
160 var chunks [][]any
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 := []any{1, 2, 3, 4}
176// isEven := func(v any) bool { return v.(int)%2 == 0 }
177// result, found := Find(numbers, isEven) // 2, true
178func Find(values []any, fn Predicate) (any, 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 := []any{1, 2, 3}
192// result := Reverse(numbers) // [3, 2, 1]
193func Reverse(values []any) []any {
194 result := make([]any, 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 := []any{1, 2, 3}
207// b := []any{"a", "b", "c"}
208// result := Zip(a, b) // [[1,"a"], [2,"b"], [3,"c"]]
209func Zip(a, b []any) [][2]any {
210 length := min(len(a), len(b))
211 result := make([][2]any, length)
212 for i := 0; i < length; i++ {
213 result[i] = [2]any{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]any{{1,"a"}, {2,"b"}, {3,"c"}}
223// numbers, letters := Unzip(pairs) // [1,2,3], ["a","b","c"]
224func Unzip(pairs [][2]any) ([]any, []any) {
225 a := make([]any, len(pairs))
226 b := make([]any, 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 := []any{1, 2, 3, 4, 5, 6}
239// byMod3 := func(v any) any { return v.(int) % 3 }
240// result := GroupBy(numbers, byMod3) // {0: [3,6], 1: [1,4], 2: [2,5]}
241func GroupBy(values []any, fn Mapper) map[any][]any {
242 result := make(map[any][]any)
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 := [][]any{{1,2}, {3,4}, {5}}
255// result := Flatten(nested) // [1,2,3,4,5]
256func Flatten(values [][]any) []any {
257 result := []any{}
258 for _, v := range values {
259 result = append(result, v...)
260 }
261 return result
262}
263
264// Helper functions
265func min(a, b int) int {
266 if a < b {
267 return a
268 }
269 return b
270}