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:
2026-03-28 02:23:26 +01:00
parent 0f9a72dfc9
commit 16e34a806e
12 changed files with 362 additions and 0 deletions
+25
View File
@@ -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
}
+35
View File
@@ -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.
+43
View File
@@ -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
}
+34
View File
@@ -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).
+16
View File
@@ -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
}
}
+40
View File
@@ -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.
+16
View File
@@ -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
}
+33
View File
@@ -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.
+13
View File
@@ -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
}
}
+36
View File
@@ -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.
+31
View File
@@ -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)
}
+40
View File
@@ -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.