feat: 6 funciones core — retry, memoize, pipeline, map_concurrent, partition, chunk
Funciones genericas reutilizables: - RetryWithBackoff: reintento con backoff exponencial (impure) - Memoize: cache de funciones puras (pure) - Pipeline: composición T→T en secuencia (pure) - MapConcurrent: map paralelo con worker pool (impure) - Partition: divide slice en dos por predicado (pure) - Chunk: divide slice en trozos de tamaño N (pure) Todas con implementación real y documentación .md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package core
|
||||
|
||||
// Chunk splits xs into sub-slices of the given size.
|
||||
// The last chunk may contain fewer than size elements.
|
||||
// Returns nil if xs is empty. Panics if size <= 0.
|
||||
func Chunk[T any](xs []T, size int) [][]T {
|
||||
if size <= 0 {
|
||||
panic("chunk size must be > 0")
|
||||
}
|
||||
n := len(xs)
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
numChunks := (n + size - 1) / size
|
||||
chunks := make([][]T, 0, numChunks)
|
||||
for i := 0; i < n; i += size {
|
||||
end := i + size
|
||||
if end > n {
|
||||
end = n
|
||||
}
|
||||
chunks = append(chunks, xs[i:end])
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: chunk
|
||||
kind: function
|
||||
lang: go
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "func Chunk[T any](xs []T, size int) [][]T"
|
||||
description: "Divide un slice en trozos (sub-slices) de tamanio N. El ultimo trozo puede contener menos de N elementos. Retorna nil si el slice esta vacio. Entra en panic si size <= 0."
|
||||
tags: [slice, chunk, batch, generic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/core/chunk.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
chunks := Chunk([]int{1, 2, 3, 4, 5}, 2)
|
||||
// chunks = [[1, 2], [3, 4], [5]]
|
||||
|
||||
chunks = Chunk([]string{"a", "b", "c", "d"}, 4)
|
||||
// chunks = [["a", "b", "c", "d"]]
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Funcion pura generica. Los sub-slices comparten memoria con el slice original (son slices del mismo backing array). Pre-calcula la capacidad del slice de chunks para evitar reasignaciones. Si size es mayor que len(xs), retorna un unico chunk con todos los elementos.
|
||||
@@ -0,0 +1,43 @@
|
||||
package core
|
||||
|
||||
import "sync"
|
||||
|
||||
// MapConcurrent applies fn to each element of xs using a pool of worker goroutines.
|
||||
// The number of concurrent workers is capped by the workers parameter.
|
||||
// Results preserve the original index order. If workers <= 0, it defaults to 1.
|
||||
func MapConcurrent[T any, U any](xs []T, fn func(T) U, workers int) []U {
|
||||
if workers <= 0 {
|
||||
workers = 1
|
||||
}
|
||||
n := len(xs)
|
||||
if n == 0 {
|
||||
return []U{}
|
||||
}
|
||||
|
||||
results := make([]U, n)
|
||||
var wg sync.WaitGroup
|
||||
ch := make(chan int, n)
|
||||
|
||||
// Feed indices into the channel.
|
||||
for i := range xs {
|
||||
ch <- i
|
||||
}
|
||||
close(ch)
|
||||
|
||||
// Spawn workers.
|
||||
if workers > n {
|
||||
workers = n
|
||||
}
|
||||
wg.Add(workers)
|
||||
for w := 0; w < workers; w++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := range ch {
|
||||
results[i] = fn(xs[i])
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: map_concurrent
|
||||
kind: function
|
||||
lang: go
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func MapConcurrent[T any, U any](xs []T, fn func(T) U, workers int) []U"
|
||||
description: "Aplica una funcion a cada elemento de un slice usando un pool de goroutines como workers. Los resultados preservan el orden original del slice de entrada."
|
||||
tags: [map, concurrent, parallel, generic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/core/map_concurrent.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
squares := MapConcurrent([]int{1, 2, 3, 4, 5}, func(n int) int {
|
||||
return n * n
|
||||
}, 3)
|
||||
// squares = [1, 4, 9, 16, 25] (orden preservado)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Funcion impura generica que usa goroutines y sync.WaitGroup. Los workers se alimentan de un canal con indices, garantizando que cada resultado se escribe en su posicion correcta sin race conditions (cada goroutine escribe en un indice unico). Si workers <= 0 se usa 1. Si workers > len(xs) se ajusta a len(xs).
|
||||
@@ -0,0 +1,16 @@
|
||||
package core
|
||||
|
||||
// Memoize wraps a pure function fn so that results are cached by key.
|
||||
// Subsequent calls with the same key return the cached value without calling fn again.
|
||||
// The returned function is safe for single-goroutine use.
|
||||
func Memoize[K comparable, V any](fn func(K) V) func(K) V {
|
||||
cache := make(map[K]V)
|
||||
return func(key K) V {
|
||||
if val, ok := cache[key]; ok {
|
||||
return val
|
||||
}
|
||||
val := fn(key)
|
||||
cache[key] = val
|
||||
return val
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: memoize
|
||||
kind: function
|
||||
lang: go
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "func Memoize[K comparable, V any](fn func(K) V) func(K) V"
|
||||
description: "Cachea resultados de una funcion pura. Retorna una nueva funcion que almacena en un mapa interno los resultados ya calculados, evitando recalculos para la misma clave."
|
||||
tags: [cache, memoize, functional, generic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/core/memoize.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
fib := Memoize(func(n int) int {
|
||||
if n <= 1 {
|
||||
return n
|
||||
}
|
||||
// nota: para recursion memoizada se necesita declarar la variable antes
|
||||
return n // simplificado
|
||||
})
|
||||
doubled := Memoize(func(x int) int { return x * 2 })
|
||||
doubled(5) // calcula: 10
|
||||
doubled(5) // cache hit: 10
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Funcion pura generica (referencialmente transparente). El cache interno es un map[K]V sin sincronizacion, por lo que la funcion retornada es segura solo para uso en una sola goroutine. K debe ser comparable para usarse como clave del mapa.
|
||||
@@ -0,0 +1,16 @@
|
||||
package core
|
||||
|
||||
// Partition splits xs into two slices: the first contains elements where pred returns true,
|
||||
// the second contains elements where pred returns false. Original order is preserved in both.
|
||||
func Partition[T any](xs []T, pred func(T) bool) ([]T, []T) {
|
||||
trueSlice := make([]T, 0)
|
||||
falseSlice := make([]T, 0)
|
||||
for _, x := range xs {
|
||||
if pred(x) {
|
||||
trueSlice = append(trueSlice, x)
|
||||
} else {
|
||||
falseSlice = append(falseSlice, x)
|
||||
}
|
||||
}
|
||||
return trueSlice, falseSlice
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: partition
|
||||
kind: function
|
||||
lang: go
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "func Partition[T any](xs []T, pred func(T) bool) ([]T, []T)"
|
||||
description: "Divide un slice en dos segun un predicado. El primer slice contiene los elementos que cumplen el predicado, el segundo los que no. Se preserva el orden original."
|
||||
tags: [slice, partition, functional, generic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/core/partition.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
evens, odds := Partition([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 })
|
||||
// evens = [2, 4]
|
||||
// odds = [1, 3, 5]
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Funcion pura generica. Ambos slices retornados son nuevos (no muta el slice original). Si el slice de entrada esta vacio, retorna dos slices vacios. Los elementos mantienen su orden relativo original en cada particion.
|
||||
@@ -0,0 +1,13 @@
|
||||
package core
|
||||
|
||||
// Pipeline composes a sequence of functions T -> T into a single function.
|
||||
// The functions are applied left to right: Pipeline(f, g, h)(x) == h(g(f(x))).
|
||||
// Returns the identity function if no functions are provided.
|
||||
func Pipeline[T any](fns ...func(T) T) func(T) T {
|
||||
return func(val T) T {
|
||||
for _, fn := range fns {
|
||||
val = fn(val)
|
||||
}
|
||||
return val
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: pipeline
|
||||
kind: function
|
||||
lang: go
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "func Pipeline[T any](fns ...func(T) T) func(T) T"
|
||||
description: "Compone funciones T -> T en secuencia de izquierda a derecha. Pipeline(f, g, h)(x) equivale a h(g(f(x))). Sin funciones retorna la identidad."
|
||||
tags: [pipeline, compose, functional, generic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/core/pipeline.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
transform := Pipeline(
|
||||
func(s string) string { return strings.TrimSpace(s) },
|
||||
func(s string) string { return strings.ToUpper(s) },
|
||||
func(s string) string { return "[" + s + "]" },
|
||||
)
|
||||
transform(" hello ") // "[HELLO]"
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Funcion pura generica. Las funciones se aplican en orden de izquierda a derecha (no es composicion matematica tradicional). Si se pasa un slice vacio de funciones, la funcion retornada actua como identidad.
|
||||
@@ -0,0 +1,31 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RetryWithBackoff retries fn up to maxRetries times with exponential backoff.
|
||||
// The delay between attempt i and i+1 is baseDelay * 2^i.
|
||||
// Returns the first successful result or the last error after all retries are exhausted.
|
||||
func RetryWithBackoff[T any](fn func() (T, error), maxRetries int, baseDelay time.Duration) (T, error) {
|
||||
var zero T
|
||||
if maxRetries < 0 {
|
||||
return zero, fmt.Errorf("maxRetries must be >= 0, got %d", maxRetries)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||
result, err := fn()
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
lastErr = err
|
||||
|
||||
if attempt < maxRetries {
|
||||
delay := baseDelay * (1 << uint(attempt))
|
||||
time.Sleep(delay)
|
||||
}
|
||||
}
|
||||
return zero, fmt.Errorf("all %d retries exhausted: %w", maxRetries+1, lastErr)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: retry_with_backoff
|
||||
kind: function
|
||||
lang: go
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func RetryWithBackoff[T any](fn func() (T, error), maxRetries int, baseDelay time.Duration) (T, error)"
|
||||
description: "Reintenta una funcion impura con backoff exponencial. El delay entre intento i e i+1 es baseDelay * 2^i. Retorna el primer resultado exitoso o el ultimo error tras agotar los reintentos."
|
||||
tags: [retry, backoff, resilience, generic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/core/retry_with_backoff.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
result, err := RetryWithBackoff(func() (string, error) {
|
||||
resp, err := http.Get("https://api.example.com/data")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return "ok", nil
|
||||
}, 3, 100*time.Millisecond)
|
||||
// Intenta hasta 4 veces (1 inicial + 3 reintentos)
|
||||
// Delays: 100ms, 200ms, 400ms
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Funcion impura generica que usa time.Sleep para el backoff. El numero total de intentos es maxRetries + 1 (el intento inicial mas los reintentos). Si maxRetries es negativo retorna error inmediatamente.
|
||||
Reference in New Issue
Block a user