diff --git a/functions/core/.gitkeep b/functions/core/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/functions/core/filter_slice.go b/functions/core/filter_slice.go new file mode 100644 index 00000000..41b1db50 --- /dev/null +++ b/functions/core/filter_slice.go @@ -0,0 +1,13 @@ +package core + +// FilterSlice returns a new slice containing only elements where pred returns true. +// Does not mutate the original slice. +func FilterSlice[T any](xs []T, pred func(T) bool) []T { + result := make([]T, 0, len(xs)) + for _, x := range xs { + if pred(x) { + result = append(result, x) + } + } + return result +} diff --git a/functions/core/filter_slice.md b/functions/core/filter_slice.md new file mode 100644 index 00000000..448873b0 --- /dev/null +++ b/functions/core/filter_slice.md @@ -0,0 +1,32 @@ +--- +name: filter_slice +kind: function +lang: go +domain: core +version: "1.0.0" +purity: pure +signature: "func FilterSlice[T any](xs []T, pred func(T) bool) []T" +description: "Filtra un slice aplicando un predicado sin mutar el original. Retorna un nuevo slice con los elementos que cumplen la condicion." +tags: [slice, functional, generic, filter] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: true +tests: ["filtra pares", "slice vacio retorna vacio", "ningun match retorna vacio"] +test_file_path: "functions/core/filter_slice_test.go" +file_path: "functions/core/filter_slice.go" +--- + +## Ejemplo + +```go +evens := FilterSlice([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 }) +// evens = [2, 4] +``` + +## Notas + +Funcion pura generica. Crea un nuevo slice con capacidad pre-alocada al tamaño del original para minimizar reallocs. diff --git a/functions/core/filter_slice_test.go b/functions/core/filter_slice_test.go new file mode 100644 index 00000000..1cceea00 --- /dev/null +++ b/functions/core/filter_slice_test.go @@ -0,0 +1,28 @@ +package core + +import ( + "testing" +) + +func TestFilterSlice(t *testing.T) { + t.Run("filtra pares", func(t *testing.T) { + got := FilterSlice([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 }) + if len(got) != 2 || got[0] != 2 || got[1] != 4 { + t.Errorf("got %v, want [2 4]", got) + } + }) + + t.Run("slice vacio retorna vacio", func(t *testing.T) { + got := FilterSlice([]int{}, func(n int) bool { return true }) + if len(got) != 0 { + t.Errorf("got %v, want []", got) + } + }) + + t.Run("ningun match retorna vacio", func(t *testing.T) { + got := FilterSlice([]int{1, 3, 5}, func(n int) bool { return n%2 == 0 }) + if len(got) != 0 { + t.Errorf("got %v, want []", got) + } + }) +} diff --git a/functions/core/map_slice.go b/functions/core/map_slice.go new file mode 100644 index 00000000..be2d32ef --- /dev/null +++ b/functions/core/map_slice.go @@ -0,0 +1,11 @@ +package core + +// MapSlice applies fn to each element of xs and returns a new slice with the results. +// Does not mutate the original slice. +func MapSlice[T any, U any](xs []T, fn func(T) U) []U { + result := make([]U, len(xs)) + for i, x := range xs { + result[i] = fn(x) + } + return result +} diff --git a/functions/core/map_slice.md b/functions/core/map_slice.md new file mode 100644 index 00000000..61904bb8 --- /dev/null +++ b/functions/core/map_slice.md @@ -0,0 +1,32 @@ +--- +name: map_slice +kind: function +lang: go +domain: core +version: "1.0.0" +purity: pure +signature: "func MapSlice[T any, U any](xs []T, fn func(T) U) []U" +description: "Transforma cada elemento de un slice aplicando una funcion. Retorna un nuevo slice del mismo tamaño con los resultados." +tags: [slice, functional, generic, map, transform] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: true +tests: ["transforma enteros a strings", "slice vacio retorna vacio", "preserva orden"] +test_file_path: "functions/core/map_slice_test.go" +file_path: "functions/core/map_slice.go" +--- + +## Ejemplo + +```go +strs := MapSlice([]int{1, 2, 3}, func(n int) string { return fmt.Sprintf("%d", n) }) +// strs = ["1", "2", "3"] +``` + +## Notas + +Funcion pura generica con dos type parameters: T (input) y U (output). Pre-aloca el slice resultado al tamaño exacto. diff --git a/functions/core/map_slice_test.go b/functions/core/map_slice_test.go new file mode 100644 index 00000000..f3e9cd15 --- /dev/null +++ b/functions/core/map_slice_test.go @@ -0,0 +1,29 @@ +package core + +import ( + "fmt" + "testing" +) + +func TestMapSlice(t *testing.T) { + t.Run("transforma enteros a strings", func(t *testing.T) { + got := MapSlice([]int{1, 2, 3}, func(n int) string { return fmt.Sprintf("%d", n) }) + if len(got) != 3 || got[0] != "1" || got[1] != "2" || got[2] != "3" { + t.Errorf("got %v", got) + } + }) + + t.Run("slice vacio retorna vacio", func(t *testing.T) { + got := MapSlice([]int{}, func(n int) int { return n * 2 }) + if len(got) != 0 { + t.Errorf("got %v, want []", got) + } + }) + + t.Run("preserva orden", func(t *testing.T) { + got := MapSlice([]int{3, 1, 2}, func(n int) int { return n * 10 }) + if got[0] != 30 || got[1] != 10 || got[2] != 20 { + t.Errorf("got %v, want [30 10 20]", got) + } + }) +} diff --git a/types/core/.gitkeep b/types/core/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/types/core/result.go b/types/core/result.go new file mode 100644 index 00000000..d79ae6d7 --- /dev/null +++ b/types/core/result.go @@ -0,0 +1,52 @@ +package core + +// Result is a sum type representing either a successful value or an error. +// Exactly one of Ok or Err is non-nil at any time. +type Result[T any] struct { + ok *T + err error +} + +// Ok creates a successful Result. +func Ok[T any](v T) Result[T] { + return Result[T]{ok: &v} +} + +// Err creates a failed Result. +func Err[T any](err error) Result[T] { + return Result[T]{err: err} +} + +// IsOk returns true if the Result contains a value. +func (r Result[T]) IsOk() bool { + return r.ok != nil +} + +// IsErr returns true if the Result contains an error. +func (r Result[T]) IsErr() bool { + return r.err != nil +} + +// Unwrap returns the value or panics if the Result is an error. +func (r Result[T]) Unwrap() T { + if r.ok == nil { + panic("called Unwrap on an Err Result") + } + return *r.ok +} + +// UnwrapErr returns the error or panics if the Result is Ok. +func (r Result[T]) UnwrapErr() error { + if r.err == nil { + panic("called UnwrapErr on an Ok Result") + } + return r.err +} + +// UnwrapOr returns the value or a default if the Result is an error. +func (r Result[T]) UnwrapOr(def T) T { + if r.ok != nil { + return *r.ok + } + return def +} diff --git a/types/core/result.md b/types/core/result.md new file mode 100644 index 00000000..b3b455db --- /dev/null +++ b/types/core/result.md @@ -0,0 +1,21 @@ +--- +name: result +lang: go +domain: core +version: "1.0.0" +algebraic: sum +definition: | + type Result[T any] struct { + ok *T + err error + } +description: "Tipo suma generico que representa exito (Ok) o fallo (Err). Permite componer operaciones que pueden fallar sin recurrir a multiples returns (T, error)." +tags: [result, sum, error-handling, functional, generic] +uses_types: [] +file_path: "types/core/result.go" +--- + +## Notas + +Tipo suma con dos variantes: Ok(T) y Err(error). Inspirado en Result de Rust. +Util para encadenar operaciones y evitar el patron `if err != nil` repetitivo. diff --git a/types/core/result_test.go b/types/core/result_test.go new file mode 100644 index 00000000..2674fa50 --- /dev/null +++ b/types/core/result_test.go @@ -0,0 +1,53 @@ +package core + +import ( + "errors" + "testing" +) + +func TestResultOk(t *testing.T) { + r := Ok(42) + if !r.IsOk() { + t.Error("expected IsOk") + } + if r.IsErr() { + t.Error("expected not IsErr") + } + if r.Unwrap() != 42 { + t.Errorf("got %d, want 42", r.Unwrap()) + } +} + +func TestResultErr(t *testing.T) { + r := Err[int](errors.New("fail")) + if r.IsOk() { + t.Error("expected not IsOk") + } + if !r.IsErr() { + t.Error("expected IsErr") + } + if r.UnwrapErr().Error() != "fail" { + t.Errorf("got %v", r.UnwrapErr()) + } +} + +func TestUnwrapOr(t *testing.T) { + ok := Ok(10) + if ok.UnwrapOr(0) != 10 { + t.Error("UnwrapOr on Ok should return value") + } + + err := Err[int](errors.New("fail")) + if err.UnwrapOr(99) != 99 { + t.Error("UnwrapOr on Err should return default") + } +} + +func TestUnwrapPanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("Unwrap on Err should panic") + } + }() + Err[int](errors.New("fail")).Unwrap() +}