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}