diff --git a/either/core.go b/either/core.go index b9e40ae..289bff9 100644 --- a/either/core.go +++ b/either/core.go @@ -31,7 +31,7 @@ type ( // String prints some debug info for the object // -// go:noinline +//go:noinline func eitherString(s *either) string { if s.isLeft { return fmt.Sprintf("Left[%T](%v)", s.value, s.value) @@ -41,7 +41,7 @@ func eitherString(s *either) string { // Format prints some debug info for the object // -// go:noinline +//go:noinline func eitherFormat(e *either, f fmt.State, c rune) { switch c { case 's': diff --git a/internal/array/array.go b/internal/array/array.go index 3cd1f6c..8d37ca4 100644 --- a/internal/array/array.go +++ b/internal/array/array.go @@ -17,6 +17,37 @@ package array func Slice[GA ~[]A, A any](low, high int) func(as GA) GA { return func(as GA) GA { + length := len(as) + + // Handle negative indices - count backward from the end + if low < 0 { + low = length + low + if low < 0 { + low = 0 + } + } + if high < 0 { + high = length + high + if high < 0 { + high = 0 + } + } + + // Start index > array length: return empty array + if low > length { + return Empty[GA, A]() + } + + // End index > array length: slice to the end + if high > length { + high = length + } + + // Start >= end: return empty array + if low >= high { + return Empty[GA, A]() + } + return as[low:high] } } diff --git a/option/core.go b/option/core.go index 06b9230..3e4a79c 100644 --- a/option/core.go +++ b/option/core.go @@ -35,7 +35,7 @@ type Option[A any] struct { // optString prints some debug info for the object // -// go:noinline +//go:noinline func optString(isSome bool, value any) string { if isSome { return fmt.Sprintf("Some[%T](%v)", value, value) @@ -45,7 +45,7 @@ func optString(isSome bool, value any) string { // optFormat prints some debug info for the object // -// go:noinline +//go:noinline func optFormat(isSome bool, value any, f fmt.State, c rune) { switch c { case 's': @@ -78,7 +78,7 @@ func (s Option[A]) MarshalJSON() ([]byte, error) { // optUnmarshalJSON unmarshals the [Option] from a JSON string // -// go:noinline +//go:noinline func optUnmarshalJSON(isSome *bool, value any, data []byte) error { // decode the value if bytes.Equal(data, jsonNull) { diff --git a/pair/pair.go b/pair/pair.go index f499ee1..3c9cdfa 100644 --- a/pair/pair.go +++ b/pair/pair.go @@ -34,14 +34,14 @@ type ( // String prints some debug info for the object // -// go:noinline +//go:noinline func pairString(s *pair) string { return fmt.Sprintf("Pair[%T, %T](%v, %v)", s.h, s.t, s.h, s.t) } // Format prints some debug info for the object // -// go:noinline +//go:noinline func pairFormat(e *pair, f fmt.State, c rune) { switch c { case 's': diff --git a/v2/array/any.go b/v2/array/any.go index ce653ab..ebe93e0 100644 --- a/v2/array/any.go +++ b/v2/array/any.go @@ -29,6 +29,8 @@ import ( // return i%2 == 0 && x%2 == 0 // }) // result := hasEvenAtEvenIndex([]int{1, 3, 4, 5}) // true (4 is at index 2) +// +//go:inline func AnyWithIndex[A any](pred func(int, A) bool) func([]A) bool { return G.AnyWithIndex[[]A](pred) } @@ -41,6 +43,8 @@ func AnyWithIndex[A any](pred func(int, A) bool) func([]A) bool { // // hasEven := array.Any(func(x int) bool { return x%2 == 0 }) // result := hasEven([]int{1, 3, 4, 5}) // true +// +//go:inline func Any[A any](pred func(A) bool) func([]A) bool { return G.Any[[]A](pred) } diff --git a/v2/array/array.go b/v2/array/array.go index f04d662..11c85ea 100644 --- a/v2/array/array.go +++ b/v2/array/array.go @@ -26,22 +26,30 @@ import ( ) // From constructs an array from a set of variadic arguments +// +//go:inline func From[A any](data ...A) []A { return G.From[[]A](data...) } // MakeBy returns a `Array` of length `n` with element `i` initialized with `f(i)`. +// +//go:inline func MakeBy[F ~func(int) A, A any](n int, f F) []A { return G.MakeBy[[]A](n, f) } // Replicate creates a `Array` containing a value repeated the specified number of times. +// +//go:inline func Replicate[A any](n int, a A) []A { return G.Replicate[[]A](n, a) } // MonadMap applies a function to each element of an array, returning a new array with the results. // This is the monadic version of Map that takes the array as the first parameter. +// +//go:inline func MonadMap[A, B any](as []A, f func(a A) B) []B { return G.MonadMap[[]A, []B](as, f) } @@ -58,6 +66,8 @@ func MonadMapRef[A, B any](as []A, f func(a *A) B) []B { } // MapWithIndex applies a function to each element and its index in an array, returning a new array with the results. +// +//go:inline func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B { return G.MapWithIndex[[]A, []B](f) } @@ -69,6 +79,8 @@ func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B { // // double := array.Map(func(x int) int { return x * 2 }) // result := double([]int{1, 2, 3}) // [2, 4, 6] +// +//go:inline func Map[A, B any](f func(a A) B) func([]A) []B { return G.Map[[]A, []B, A, B](f) } @@ -104,11 +116,15 @@ func filterMapRef[A, B any](fa []A, pred func(a *A) bool, f func(a *A) B) []B { } // Filter returns a new array with all elements from the original array that match a predicate +// +//go:inline func Filter[A any](pred func(A) bool) EM.Endomorphism[[]A] { return G.Filter[[]A](pred) } // FilterWithIndex returns a new array with all elements from the original array that match a predicate +// +//go:inline func FilterWithIndex[A any](pred func(int, A) bool) EM.Endomorphism[[]A] { return G.FilterWithIndex[[]A](pred) } @@ -120,27 +136,37 @@ func FilterRef[A any](pred func(*A) bool) EM.Endomorphism[[]A] { // MonadFilterMap maps an array with a function that returns an Option and keeps only the Some values. // This is the monadic version that takes the array as the first parameter. +// +//go:inline func MonadFilterMap[A, B any](fa []A, f func(A) O.Option[B]) []B { return G.MonadFilterMap[[]A, []B](fa, f) } // MonadFilterMapWithIndex maps an array with a function that takes an index and returns an Option, // keeping only the Some values. This is the monadic version that takes the array as the first parameter. +// +//go:inline func MonadFilterMapWithIndex[A, B any](fa []A, f func(int, A) O.Option[B]) []B { return G.MonadFilterMapWithIndex[[]A, []B](fa, f) } // FilterMap maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones. +// +//go:inline func FilterMap[A, B any](f func(A) O.Option[B]) func([]A) []B { return G.FilterMap[[]A, []B](f) } // FilterMapWithIndex maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones. +// +//go:inline func FilterMapWithIndex[A, B any](f func(int, A) O.Option[B]) func([]A) []B { return G.FilterMapWithIndex[[]A, []B](f) } // FilterChain maps an array with an iterating function that returns an [O.Option] of an array. It keeps only the Some values discarding the Nones and then flattens the result. +// +//go:inline func FilterChain[A, B any](f func(A) O.Option[[]B]) func([]A) []B { return G.FilterChain[[]A](f) } @@ -161,6 +187,7 @@ func reduceRef[A, B any](fa []A, f func(B, *A) B, initial B) B { return current } +//go:inline func MonadReduce[A, B any](fa []A, f func(B, A) B, initial B) B { return G.MonadReduce(fa, f, initial) } @@ -171,23 +198,31 @@ func MonadReduce[A, B any](fa []A, f func(B, A) B, initial B) B { // // sum := array.Reduce(func(acc, x int) int { return acc + x }, 0) // result := sum([]int{1, 2, 3, 4, 5}) // 15 +// +//go:inline func Reduce[A, B any](f func(B, A) B, initial B) func([]A) B { return G.Reduce[[]A](f, initial) } // ReduceWithIndex folds an array from left to right with access to the index, // applying a function to accumulate a result. +// +//go:inline func ReduceWithIndex[A, B any](f func(int, B, A) B, initial B) func([]A) B { return G.ReduceWithIndex[[]A](f, initial) } // ReduceRight folds an array from right to left, applying a function to accumulate a result. +// +//go:inline func ReduceRight[A, B any](f func(A, B) B, initial B) func([]A) B { return G.ReduceRight[[]A](f, initial) } // ReduceRightWithIndex folds an array from right to left with access to the index, // applying a function to accumulate a result. +// +//go:inline func ReduceRightWithIndex[A, B any](f func(int, A, B) B, initial B) func([]A) B { return G.ReduceRightWithIndex[[]A](f, initial) } @@ -201,11 +236,15 @@ func ReduceRef[A, B any](f func(B, *A) B, initial B) func([]A) B { } // Append adds an element to the end of an array, returning a new array. +// +//go:inline func Append[A any](as []A, a A) []A { return G.Append(as, a) } // IsEmpty checks if an array has no elements. +// +//go:inline func IsEmpty[A any](as []A) bool { return G.IsEmpty(as) } @@ -216,6 +255,8 @@ func IsNonEmpty[A any](as []A) bool { } // Empty returns an empty array of type A. +// +//go:inline func Empty[A any]() []A { return G.Empty[[]A]() } @@ -226,12 +267,16 @@ func Zero[A any]() []A { } // Of constructs a single element array +// +//go:inline func Of[A any](a A) []A { return G.Of[[]A](a) } // MonadChain applies a function that returns an array to each element and flattens the results. // This is the monadic version that takes the array as the first parameter (also known as FlatMap). +// +//go:inline func MonadChain[A, B any](fa []A, f func(a A) []B) []B { return G.MonadChain[[]A, []B](fa, f) } @@ -243,52 +288,70 @@ func MonadChain[A, B any](fa []A, f func(a A) []B) []B { // // duplicate := array.Chain(func(x int) []int { return []int{x, x} }) // result := duplicate([]int{1, 2, 3}) // [1, 1, 2, 2, 3, 3] +// +//go:inline func Chain[A, B any](f func(A) []B) func([]A) []B { return G.Chain[[]A, []B](f) } // MonadAp applies an array of functions to an array of values, producing all combinations. // This is the monadic version that takes both arrays as parameters. +// +//go:inline func MonadAp[B, A any](fab []func(A) B, fa []A) []B { return G.MonadAp[[]B](fab, fa) } // Ap applies an array of functions to an array of values, producing all combinations. // This is the curried version. +// +//go:inline func Ap[B, A any](fa []A) func([]func(A) B) []B { return G.Ap[[]B, []func(A) B](fa) } // Match performs pattern matching on an array, calling onEmpty if empty or onNonEmpty if not. +// +//go:inline func Match[A, B any](onEmpty func() B, onNonEmpty func([]A) B) func([]A) B { return G.Match[[]A](onEmpty, onNonEmpty) } // MatchLeft performs pattern matching on an array, calling onEmpty if empty or onNonEmpty with head and tail if not. +// +//go:inline func MatchLeft[A, B any](onEmpty func() B, onNonEmpty func(A, []A) B) func([]A) B { return G.MatchLeft[[]A](onEmpty, onNonEmpty) } // Tail returns all elements except the first, wrapped in an Option. // Returns None if the array is empty. +// +//go:inline func Tail[A any](as []A) O.Option[[]A] { return G.Tail(as) } // Head returns the first element of an array, wrapped in an Option. // Returns None if the array is empty. +// +//go:inline func Head[A any](as []A) O.Option[A] { return G.Head(as) } // First returns the first element of an array, wrapped in an Option (alias for Head). // Returns None if the array is empty. +// +//go:inline func First[A any](as []A) O.Option[A] { return G.First(as) } // Last returns the last element of an array, wrapped in an Option. // Returns None if the array is empty. +// +//go:inline func Last[A any](as []A) O.Option[A] { return G.Last(as) } @@ -336,6 +399,8 @@ func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A { // Example: // // result := array.Flatten([][]int{{1, 2}, {3, 4}, {5}}) // [1, 2, 3, 4, 5] +// +//go:inline func Flatten[A any](mma [][]A) []A { return G.Flatten(mma) } @@ -347,17 +412,23 @@ func Slice[A any](low, high int) func(as []A) []A { // Lookup returns the element at the specified index, wrapped in an Option. // Returns None if the index is out of bounds. +// +//go:inline func Lookup[A any](idx int) func([]A) O.Option[A] { return G.Lookup[[]A](idx) } // UpsertAt returns a function that inserts or updates an element at a specific index. // If the index is out of bounds, the element is appended. +// +//go:inline func UpsertAt[A any](a A) EM.Endomorphism[[]A] { return G.UpsertAt[[]A](a) } // Size returns the number of elements in an array. +// +//go:inline func Size[A any](as []A) int { return G.Size(as) } @@ -365,12 +436,16 @@ func Size[A any](as []A) int { // MonadPartition splits an array into two arrays based on a predicate. // The first array contains elements for which the predicate returns false, // the second contains elements for which it returns true. +// +//go:inline func MonadPartition[A any](as []A, pred func(A) bool) tuple.Tuple2[[]A, []A] { return G.MonadPartition(as, pred) } // Partition creates two new arrays out of one, the left result contains the elements // for which the predicate returns false, the right one those for which the predicate returns true +// +//go:inline func Partition[A any](pred func(A) bool) func([]A) tuple.Tuple2[[]A, []A] { return G.Partition[[]A](pred) } @@ -391,53 +466,73 @@ func ConstNil[A any]() []A { } // SliceRight extracts a subarray from the specified start index to the end. +// +//go:inline func SliceRight[A any](start int) EM.Endomorphism[[]A] { return G.SliceRight[[]A](start) } // Copy creates a shallow copy of the array +// +//go:inline func Copy[A any](b []A) []A { return G.Copy(b) } // Clone creates a deep copy of the array using the provided endomorphism to clone the values +// +//go:inline func Clone[A any](f func(A) A) func(as []A) []A { return G.Clone[[]A](f) } // FoldMap maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid. +// +//go:inline func FoldMap[A, B any](m M.Monoid[B]) func(func(A) B) func([]A) B { return G.FoldMap[[]A](m) } // FoldMapWithIndex maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid. +// +//go:inline func FoldMapWithIndex[A, B any](m M.Monoid[B]) func(func(int, A) B) func([]A) B { return G.FoldMapWithIndex[[]A](m) } // Fold folds the array using the provided Monoid. +// +//go:inline func Fold[A any](m M.Monoid[A]) func([]A) A { return G.Fold[[]A](m) } // Push adds an element to the end of an array (alias for Append). +// +//go:inline func Push[A any](a A) EM.Endomorphism[[]A] { return G.Push[EM.Endomorphism[[]A]](a) } // MonadFlap applies a value to an array of functions, producing an array of results. // This is the monadic version that takes both parameters. +// +//go:inline func MonadFlap[B, A any](fab []func(A) B, a A) []B { return G.MonadFlap[func(A) B, []func(A) B, []B, A, B](fab, a) } // Flap applies a value to an array of functions, producing an array of results. // This is the curried version. +// +//go:inline func Flap[B, A any](a A) func([]func(A) B) []B { return G.Flap[func(A) B, []func(A) B, []B, A, B](a) } // Prepend adds an element to the beginning of an array, returning a new array. +// +//go:inline func Prepend[A any](head A) EM.Endomorphism[[]A] { return G.Prepend[EM.Endomorphism[[]A]](head) } diff --git a/v2/array/bind.go b/v2/array/bind.go index 61d063b..fda8932 100644 --- a/v2/array/bind.go +++ b/v2/array/bind.go @@ -29,6 +29,8 @@ import ( // Y int // } // result := array.Do(State{}) +// +//go:inline func Do[S any]( empty S, ) []S { @@ -50,6 +52,8 @@ func Do[S any]( // func(s struct{}) []int { return []int{1, 2} }, // ), // ) +// +//go:inline func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) []T, @@ -70,6 +74,8 @@ func Bind[S1, S2, T any]( // }, // func(s struct{ X int }) int { return s.X * 2 }, // ) +// +//go:inline func Let[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) T, @@ -90,6 +96,8 @@ func Let[S1, S2, T any]( // }, // "constant", // ) +// +//go:inline func LetTo[S1, S2, T any]( setter func(T) func(S1) S2, b T, @@ -108,6 +116,8 @@ func LetTo[S1, S2, T any]( // return struct{ X int }{x} // }), // ) +// +//go:inline func BindTo[S1, T any]( setter func(T) S1, ) func([]T) []S1 { @@ -128,6 +138,8 @@ func BindTo[S1, T any]( // }, // []int{10, 20}, // ) +// +//go:inline func ApS[S1, S2, T any]( setter func(T) func(S1) S2, fa []T, diff --git a/v2/array/find.go b/v2/array/find.go index 19d3c3c..83f6004 100644 --- a/v2/array/find.go +++ b/v2/array/find.go @@ -28,6 +28,8 @@ import ( // findGreaterThan3 := array.FindFirst(func(x int) bool { return x > 3 }) // result := findGreaterThan3([]int{1, 2, 4, 5}) // Some(4) // result2 := findGreaterThan3([]int{1, 2, 3}) // None +// +//go:inline func FindFirst[A any](pred func(A) bool) func([]A) O.Option[A] { return G.FindFirst[[]A](pred) } @@ -41,6 +43,8 @@ func FindFirst[A any](pred func(A) bool) func([]A) O.Option[A] { // return i%2 == 0 && x%2 == 0 // }) // result := findEvenAtEvenIndex([]int{1, 3, 4, 5}) // Some(4) +// +//go:inline func FindFirstWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] { return G.FindFirstWithIndex[[]A](pred) } @@ -59,12 +63,16 @@ func FindFirstWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] { // return option.None[int]() // }) // result := parseFirst([]string{"a", "42", "b"}) // Some(42) +// +//go:inline func FindFirstMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] { return G.FindFirstMap[[]A](sel) } // FindFirstMapWithIndex finds the first element for which the selector function returns Some. // The selector receives both the index and the element. +// +//go:inline func FindFirstMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] { return G.FindFirstMapWithIndex[[]A](sel) } @@ -76,24 +84,32 @@ func FindFirstMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.O // // findGreaterThan3 := array.FindLast(func(x int) bool { return x > 3 }) // result := findGreaterThan3([]int{1, 4, 2, 5}) // Some(5) +// +//go:inline func FindLast[A any](pred func(A) bool) func([]A) O.Option[A] { return G.FindLast[[]A](pred) } // FindLastWithIndex finds the last element which satisfies a predicate function that also receives the index. // Returns Some(element) if found, None if no element matches. +// +//go:inline func FindLastWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] { return G.FindLastWithIndex[[]A](pred) } // FindLastMap finds the last element for which the selector function returns Some. // This combines finding and mapping in a single operation, searching from the end. +// +//go:inline func FindLastMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] { return G.FindLastMap[[]A](sel) } // FindLastMapWithIndex finds the last element for which the selector function returns Some. // The selector receives both the index and the element, searching from the end. +// +//go:inline func FindLastMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] { return G.FindLastMapWithIndex[[]A](sel) } diff --git a/v2/array/generic/array.go b/v2/array/generic/array.go index 11acf19..85dde48 100644 --- a/v2/array/generic/array.go +++ b/v2/array/generic/array.go @@ -296,16 +296,14 @@ func MatchLeft[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(A, AS) B) fu } } +//go:inline func Slice[AS ~[]A, A any](start int, end int) func(AS) AS { - return func(a AS) AS { - return a[start:end] - } + return array.Slice[AS](start, end) } +//go:inline func SliceRight[AS ~[]A, A any](start int) func(AS) AS { - return func(a AS) AS { - return a[start:] - } + return array.SliceRight[AS](start) } func Copy[AS ~[]A, A any](b AS) AS { diff --git a/v2/array/magma.go b/v2/array/magma.go index 37d1432..71a47ac 100644 --- a/v2/array/magma.go +++ b/v2/array/magma.go @@ -33,6 +33,8 @@ import ( // // Concatenate all strings // concatStrings := array.ConcatAll(monoid.MonoidString()) // result2 := concatStrings([]string{"Hello", " ", "World"}) // "Hello World" +// +//go:inline func ConcatAll[A any](m M.Monoid[A]) func([]A) A { return Reduce(m.Concat, m.Empty()) } diff --git a/v2/array/monad.go b/v2/array/monad.go index e37ac9b..d86897a 100644 --- a/v2/array/monad.go +++ b/v2/array/monad.go @@ -34,6 +34,8 @@ import ( // return []string{fmt.Sprintf("%d", x), fmt.Sprintf("%d!", x)} // }) // // Result: ["1", "1!", "2", "2!", "3", "3!"] +// +//go:inline func Monad[A, B any]() monad.Monad[A, B, []A, []B, []func(A) B] { return G.Monad[A, B, []A, []B, []func(A) B]() } diff --git a/v2/array/slice_test.go b/v2/array/slice_test.go new file mode 100644 index 0000000..22066fb --- /dev/null +++ b/v2/array/slice_test.go @@ -0,0 +1,407 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package array + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestSliceBasicCases tests normal slicing operations +func TestSliceBasicCases(t *testing.T) { + data := []int{0, 1, 2, 3, 4, 5} + + t.Run("normal slice from middle", func(t *testing.T) { + assert.Equal(t, []int{1, 2, 3}, Slice[int](1, 4)(data)) + }) + + t.Run("slice from start", func(t *testing.T) { + assert.Equal(t, []int{0, 1, 2}, Slice[int](0, 3)(data)) + }) + + t.Run("slice to end", func(t *testing.T) { + assert.Equal(t, []int{3, 4, 5}, Slice[int](3, 6)(data)) + }) + + t.Run("slice single element", func(t *testing.T) { + assert.Equal(t, []int{2}, Slice[int](2, 3)(data)) + }) + + t.Run("slice entire array", func(t *testing.T) { + assert.Equal(t, []int{0, 1, 2, 3, 4, 5}, Slice[int](0, 6)(data)) + }) +} + +// TestSliceNegativeIndices tests negative index handling (counting from end) +func TestSliceNegativeIndices(t *testing.T) { + data := []int{0, 1, 2, 3, 4, 5} + + t.Run("negative start index", func(t *testing.T) { + // -2 means length + (-2) = 6 - 2 = 4 + assert.Equal(t, []int{4, 5}, Slice[int](-2, 6)(data)) + }) + + t.Run("negative end index", func(t *testing.T) { + // -2 means length + (-2) = 6 - 2 = 4 + assert.Equal(t, []int{0, 1, 2, 3}, Slice[int](0, -2)(data)) + }) + + t.Run("both negative indices", func(t *testing.T) { + // -4 = 2, -2 = 4 + assert.Equal(t, []int{2, 3}, Slice[int](-4, -2)(data)) + }) + + t.Run("negative index beyond array start", func(t *testing.T) { + // -10 would be -4, clamped to 0 + assert.Equal(t, []int{0, 1, 2}, Slice[int](-10, 3)(data)) + }) + + t.Run("negative end index beyond array start", func(t *testing.T) { + // -10 would be -4, clamped to 0 + assert.Equal(t, []int{}, Slice[int](0, -10)(data)) + }) +} + +// TestSliceEmptyArray tests slicing on empty arrays (totality proof) +func TestSliceEmptyArray(t *testing.T) { + empty := []int{} + + t.Run("slice empty array with zero indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](0, 0)(empty)) + }) + + t.Run("slice empty array with positive indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](0, 5)(empty)) + }) + + t.Run("slice empty array with negative indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](-1, -1)(empty)) + }) + + t.Run("slice empty array with mixed indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](-5, 5)(empty)) + }) +} + +// TestSliceOutOfBounds tests out-of-bounds scenarios (totality proof) +func TestSliceOutOfBounds(t *testing.T) { + data := []int{0, 1, 2, 3, 4} + + t.Run("start index beyond array length", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](10, 15)(data)) + }) + + t.Run("end index beyond array length", func(t *testing.T) { + assert.Equal(t, []int{2, 3, 4}, Slice[int](2, 100)(data)) + }) + + t.Run("both indices beyond array length", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](10, 20)(data)) + }) + + t.Run("start equals array length", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](5, 10)(data)) + }) + + t.Run("end equals array length", func(t *testing.T) { + assert.Equal(t, []int{3, 4}, Slice[int](3, 5)(data)) + }) +} + +// TestSliceInvalidRanges tests invalid range scenarios (totality proof) +func TestSliceInvalidRanges(t *testing.T) { + data := []int{0, 1, 2, 3, 4} + + t.Run("start equals end", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](2, 2)(data)) + }) + + t.Run("start greater than end", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](4, 2)(data)) + }) + + t.Run("start greater than end with negative indices", func(t *testing.T) { + // -1 = 4, -3 = 2 + assert.Equal(t, []int{}, Slice[int](-1, -3)(data)) + }) + + t.Run("zero range at start", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](0, 0)(data)) + }) + + t.Run("zero range at end", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](5, 5)(data)) + }) +} + +// TestSliceEdgeCases tests additional edge cases (totality proof) +func TestSliceEdgeCases(t *testing.T) { + t.Run("single element array - slice all", func(t *testing.T) { + data := []int{42} + assert.Equal(t, []int{42}, Slice[int](0, 1)(data)) + }) + + t.Run("single element array - slice none", func(t *testing.T) { + data := []int{42} + assert.Equal(t, []int{}, Slice[int](1, 1)(data)) + }) + + t.Run("single element array - negative indices", func(t *testing.T) { + data := []int{42} + assert.Equal(t, []int{42}, Slice[int](-1, 1)(data)) + }) + + t.Run("large array slice", func(t *testing.T) { + data := MakeBy(1000, func(i int) int { return i }) + result := Slice[int](100, 200)(data) + assert.Equal(t, 100, len(result)) + assert.Equal(t, 100, result[0]) + assert.Equal(t, 199, result[99]) + }) +} + +// TestSliceWithDifferentTypes tests that Slice works with different types (totality proof) +func TestSliceWithDifferentTypes(t *testing.T) { + t.Run("string slice", func(t *testing.T) { + data := []string{"a", "b", "c", "d", "e"} + assert.Equal(t, []string{"b", "c", "d"}, Slice[string](1, 4)(data)) + }) + + t.Run("float slice", func(t *testing.T) { + data := []float64{1.1, 2.2, 3.3, 4.4, 5.5} + assert.Equal(t, []float64{2.2, 3.3}, Slice[float64](1, 3)(data)) + }) + + t.Run("bool slice", func(t *testing.T) { + data := []bool{true, false, true, false} + assert.Equal(t, []bool{false, true}, Slice[bool](1, 3)(data)) + }) + + t.Run("struct slice", func(t *testing.T) { + type Point struct{ X, Y int } + data := []Point{{1, 2}, {3, 4}, {5, 6}} + assert.Equal(t, []Point{{3, 4}}, Slice[Point](1, 2)(data)) + }) + + t.Run("pointer slice", func(t *testing.T) { + a, b, c := 1, 2, 3 + data := []*int{&a, &b, &c} + result := Slice[*int](1, 3)(data) + assert.Equal(t, 2, len(result)) + assert.Equal(t, 2, *result[0]) + assert.Equal(t, 3, *result[1]) + }) +} + +// TestSliceNilArray tests behavior with nil arrays (totality proof) +func TestSliceNilArray(t *testing.T) { + var nilArray []int + + t.Run("slice nil array with zero indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](0, 0)(nilArray)) + }) + + t.Run("slice nil array with positive indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](0, 5)(nilArray)) + }) + + t.Run("slice nil array with negative indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](-1, 1)(nilArray)) + }) + + t.Run("slice nil array with out of bounds indices", func(t *testing.T) { + assert.Equal(t, []int{}, Slice[int](10, 20)(nilArray)) + }) +} + +// TestSliceComposition tests that Slice can be composed with other functions +func TestSliceComposition(t *testing.T) { + data := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + + t.Run("compose multiple slices", func(t *testing.T) { + // First slice [2:8], then slice [1:4] of result + slice1 := Slice[int](2, 8) + slice2 := Slice[int](1, 4) + result := slice2(slice1(data)) + // [2,3,4,5,6,7] -> [3,4,5] + assert.Equal(t, []int{3, 4, 5}, result) + }) + + t.Run("slice then map", func(t *testing.T) { + sliced := Slice[int](2, 5)(data) + mapped := Map(func(x int) int { return x * 2 })(sliced) + assert.Equal(t, []int{4, 6, 8}, mapped) + }) + + t.Run("slice then filter", func(t *testing.T) { + sliced := Slice[int](0, 6)(data) + filtered := Filter(func(x int) bool { return x%2 == 0 })(sliced) + assert.Equal(t, []int{0, 2, 4}, filtered) + }) +} + +// TestSliceImmutability tests that Slice doesn't modify the original array +func TestSliceImmutability(t *testing.T) { + original := []int{0, 1, 2, 3, 4} + originalCopy := []int{0, 1, 2, 3, 4} + + t.Run("slicing doesn't modify original", func(t *testing.T) { + result := Slice[int](1, 4)(original) + assert.Equal(t, []int{1, 2, 3}, result) + assert.Equal(t, originalCopy, original) + }) + + t.Run("slice shares underlying array with original", func(t *testing.T) { + // Note: Go's slice operation creates a view of the underlying array, + // not a deep copy. This is expected behavior and matches Go's built-in slice semantics. + result := Slice[int](1, 4)(original) + result[0] = 999 + // The original array is affected because slices share the underlying array + assert.Equal(t, 999, original[1], "Slices share underlying array (expected Go behavior)") + }) +} + +// TestSliceTotality is a comprehensive test proving Slice is a total function +// A total function is defined for all possible inputs and never panics +func TestSliceTotality(t *testing.T) { + testCases := []struct { + name string + data []int + low int + high int + panic bool // Should always be false for a total function + }{ + // Normal cases + {"normal range", []int{1, 2, 3, 4, 5}, 1, 3, false}, + {"full range", []int{1, 2, 3}, 0, 3, false}, + {"empty result", []int{1, 2, 3}, 1, 1, false}, + + // Edge cases with empty/nil arrays + {"empty array", []int{}, 0, 0, false}, + {"empty array with indices", []int{}, 1, 5, false}, + {"nil array", nil, 0, 5, false}, + + // Negative indices + {"negative low", []int{1, 2, 3, 4, 5}, -2, 5, false}, + {"negative high", []int{1, 2, 3, 4, 5}, 0, -1, false}, + {"both negative", []int{1, 2, 3, 4, 5}, -3, -1, false}, + {"negative beyond bounds", []int{1, 2, 3}, -100, -50, false}, + + // Out of bounds + {"low beyond length", []int{1, 2, 3}, 10, 20, false}, + {"high beyond length", []int{1, 2, 3}, 1, 100, false}, + {"both beyond length", []int{1, 2, 3}, 10, 20, false}, + + // Invalid ranges + {"low equals high", []int{1, 2, 3}, 2, 2, false}, + {"low greater than high", []int{1, 2, 3}, 3, 1, false}, + {"negative invalid range", []int{1, 2, 3, 4, 5}, -1, -3, false}, + + // Extreme values + {"very large indices", []int{1, 2, 3}, 1000000, 2000000, false}, + {"very negative indices", []int{1, 2, 3}, -1000000, -500000, false}, + {"mixed extreme", []int{1, 2, 3}, -1000000, 1000000, false}, + + // Zero values + {"zero indices", []int{1, 2, 3}, 0, 0, false}, + {"zero low", []int{1, 2, 3}, 0, 3, false}, + {"zero high", []int{1, 2, 3}, 0, 0, false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // This test proves totality by ensuring no panic occurs + defer func() { + if r := recover(); r != nil { + if !tc.panic { + t.Errorf("Slice panicked unexpectedly: %v", r) + } + } else { + if tc.panic { + t.Errorf("Slice should have panicked but didn't") + } + } + }() + + // Execute the function - if it's total, it will never panic + result := Slice[int](tc.low, tc.high)(tc.data) + + // Additional verification: result should always be a valid slice + assert.NotNil(t, result, "Result should never be nil") + assert.True(t, len(result) >= 0, "Result length should be non-negative") + }) + } +} + +// TestSlicePropertyBased tests mathematical properties of Slice +func TestSlicePropertyBased(t *testing.T) { + data := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + + t.Run("identity: Slice(0, len) returns copy of array", func(t *testing.T) { + result := Slice[int](0, len(data))(data) + assert.Equal(t, data, result) + }) + + t.Run("empty: Slice(i, i) always returns empty", func(t *testing.T) { + for i := 0; i <= len(data); i++ { + result := Slice[int](i, i)(data) + assert.Equal(t, []int{}, result) + } + }) + + t.Run("length property: len(Slice(i, j)) = max(0, min(j, len) - max(i, 0))", func(t *testing.T) { + testCases := []struct{ low, high, expected int }{ + {0, 5, 5}, + {2, 7, 5}, + {5, 5, 0}, + {3, 2, 0}, // invalid range + {-2, 10, 2}, // -2 becomes 8, so slice [8:10] has length 2 + {0, 100, 10}, + } + + for _, tc := range testCases { + result := Slice[int](tc.low, tc.high)(data) + assert.Equal(t, tc.expected, len(result), + "Slice(%d, %d) should have length %d", tc.low, tc.high, tc.expected) + } + }) + + t.Run("concatenation: Slice(0,i) + Slice(i,len) = original", func(t *testing.T) { + for i := 0; i <= len(data); i++ { + left := Slice[int](0, i)(data) + right := Slice[int](i, len(data))(data) + concatenated := append(left, right...) + assert.Equal(t, data, concatenated) + } + }) + + t.Run("subset property: all elements in slice are in original", func(t *testing.T) { + result := Slice[int](2, 7)(data) + for _, elem := range result { + found := false + for _, orig := range data { + if elem == orig { + found = true + break + } + } + assert.True(t, found, "Element %d should be in original array", elem) + } + }) +} + +// Made with Bob diff --git a/v2/array/sort.go b/v2/array/sort.go index ad06612..d0d07a6 100644 --- a/v2/array/sort.go +++ b/v2/array/sort.go @@ -30,6 +30,8 @@ import ( // numbers := []int{3, 1, 4, 1, 5, 9, 2, 6} // sorted := array.Sort(ord.FromStrictCompare[int]())(numbers) // // Result: [1, 1, 2, 3, 4, 5, 6, 9] +// +//go:inline func Sort[T any](ord O.Ord[T]) func(ma []T) []T { return G.Sort[[]T](ord) } @@ -58,6 +60,8 @@ func Sort[T any](ord O.Ord[T]) func(ma []T) []T { // ) // sorted := sortByAge(people) // // Result: [{"Bob", 25}, {"Alice", 30}, {"Charlie", 35}] +// +//go:inline func SortByKey[K, T any](ord O.Ord[K], f func(T) K) func(ma []T) []T { return G.SortByKey[[]T](ord, f) } @@ -87,6 +91,8 @@ func SortByKey[K, T any](ord O.Ord[K], f func(T) K) func(ma []T) []T { // }) // sorted := sortByName(people) // // Result: [{"Jones", "Bob"}, {"Smith", "Alice"}, {"Smith", "John"}] +// +//go:inline func SortBy[T any](ord []O.Ord[T]) func(ma []T) []T { return G.SortBy[[]T, []O.Ord[T]](ord) } diff --git a/v2/array/traverse.go b/v2/array/traverse.go index 954b352..22a3238 100644 --- a/v2/array/traverse.go +++ b/v2/array/traverse.go @@ -52,6 +52,8 @@ import ( // // result := parseAll([]string{"1", "2", "3"}) // Some([1, 2, 3]) // result2 := parseAll([]string{"1", "x", "3"}) // None +// +//go:inline func Traverse[A, B, HKTB, HKTAB, HKTRB any]( fof func([]B) HKTRB, fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB, @@ -66,6 +68,8 @@ func Traverse[A, B, HKTB, HKTAB, HKTRB any]( // into an effect of an array. // // This is useful when you want to apply the traverse operation directly without currying. +// +//go:inline func MonadTraverse[A, B, HKTB, HKTAB, HKTRB any]( fof func([]B) HKTRB, fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB, diff --git a/v2/array/uniq.go b/v2/array/uniq.go index c54fcb9..775c44d 100644 --- a/v2/array/uniq.go +++ b/v2/array/uniq.go @@ -15,6 +15,8 @@ import ( // // strings := []string{"a", "b", "a", "c", "b"} // unique2 := array.StrictUniq(strings) // ["a", "b", "c"] +// +//go:inline func StrictUniq[A comparable](as []A) []A { return G.StrictUniq[[]A](as) } @@ -42,6 +44,8 @@ func StrictUniq[A comparable](as []A) []A { // uniqueByName := array.Uniq(func(p Person) string { return p.Name }) // result := uniqueByName(people) // // Result: [{"Alice", 30}, {"Bob", 25}, {"Charlie", 30}] +// +//go:inline func Uniq[A any, K comparable](f func(A) K) func(as []A) []A { return G.Uniq[[]A](f) } diff --git a/v2/array/zip.go b/v2/array/zip.go index a85288b..c898fe1 100644 --- a/v2/array/zip.go +++ b/v2/array/zip.go @@ -33,6 +33,8 @@ import ( // return fmt.Sprintf("%s is %d years old", name, age) // }) // // Result: ["Alice is 30 years old", "Bob is 25 years old", "Charlie is 35 years old"] +// +//go:inline func ZipWith[FCT ~func(A, B) C, A, B, C any](fa []A, fb []B, f FCT) []C { return G.ZipWith[[]A, []B, []C, FCT](fa, fb, f) } @@ -51,6 +53,8 @@ func ZipWith[FCT ~func(A, B) C, A, B, C any](fa []A, fb []B, f FCT) []C { // // With different lengths // pairs2 := array.Zip([]int{1, 2})([]string{"a", "b", "c"}) // // Result: [(a, 1), (b, 2)] +// +//go:inline func Zip[A, B any](fb []B) func([]A) []T.Tuple2[A, B] { return G.Zip[[]A, []B, []T.Tuple2[A, B]](fb) } @@ -72,6 +76,8 @@ func Zip[A, B any](fb []B) func([]A) []T.Tuple2[A, B] { // // Result: (["Alice", "Bob", "Charlie"], [30, 25, 35]) // names := result.Head // ["Alice", "Bob", "Charlie"] // ages := result.Tail // [30, 25, 35] +// +//go:inline func Unzip[A, B any](cs []T.Tuple2[A, B]) T.Tuple2[[]A, []B] { return G.Unzip[[]A, []B, []T.Tuple2[A, B]](cs) } diff --git a/v2/either/apply.go b/v2/either/apply.go index 9667d44..7c4cea6 100644 --- a/v2/either/apply.go +++ b/v2/either/apply.go @@ -29,6 +29,8 @@ import ( // intAdd := semigroup.MakeSemigroup(func(a, b int) int { return a + b }) // eitherSemi := either.ApplySemigroup[error](intAdd) // result := eitherSemi.Concat(either.Right[error](2), either.Right[error](3)) // Right(5) +// +//go:inline func ApplySemigroup[E, A any](s S.Semigroup[A]) S.Semigroup[Either[E, A]] { return S.ApplySemigroup(MonadMap[E, A, func(A) A], MonadAp[A, E, A], s) } @@ -41,6 +43,8 @@ func ApplySemigroup[E, A any](s S.Semigroup[A]) S.Semigroup[Either[E, A]] { // intAddMonoid := monoid.MakeMonoid(0, func(a, b int) int { return a + b }) // eitherMon := either.ApplicativeMonoid[error](intAddMonoid) // empty := eitherMon.Empty() // Right(0) +// +//go:inline func ApplicativeMonoid[E, A any](m M.Monoid[A]) M.Monoid[Either[E, A]] { return M.ApplicativeMonoid(Of[E, A], MonadMap[E, A, func(A) A], MonadAp[A, E, A], m) } diff --git a/v2/either/array.go b/v2/either/array.go index c72d711..ea84890 100644 --- a/v2/either/array.go +++ b/v2/either/array.go @@ -33,6 +33,8 @@ import ( // } // result := either.TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"}) // // result is Right([]int{1, 2, 3}) +// +//go:inline func TraverseArrayG[GA ~[]A, GB ~[]B, E, A, B any](f func(A) Either[E, B]) func(GA) Either[E, GB] { return RA.Traverse[GA]( Of[E, GB], @@ -55,6 +57,8 @@ func TraverseArrayG[GA ~[]A, GB ~[]B, E, A, B any](f func(A) Either[E, B]) func( // } // result := either.TraverseArray(parse)([]string{"1", "2", "3"}) // // result is Right([]int{1, 2, 3}) +// +//go:inline func TraverseArray[E, A, B any](f func(A) Either[E, B]) func([]A) Either[E, []B] { return TraverseArrayG[[]A, []B](f) } @@ -74,6 +78,8 @@ func TraverseArray[E, A, B any](f func(A) Either[E, B]) func([]A) Either[E, []B] // } // result := either.TraverseArrayWithIndexG[[]string, []string](validate)([]string{"a", "b"}) // // result is Right([]string{"0:a", "1:b"}) +// +//go:inline func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, E, A, B any](f func(int, A) Either[E, B]) func(GA) Either[E, GB] { return RA.TraverseWithIndex[GA]( Of[E, GB], @@ -98,6 +104,8 @@ func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, E, A, B any](f func(int, A) Eithe // } // result := either.TraverseArrayWithIndex(validate)([]string{"a", "b"}) // // result is Right([]string{"0:a", "1:b"}) +// +//go:inline func TraverseArrayWithIndex[E, A, B any](f func(int, A) Either[E, B]) func([]A) Either[E, []B] { return TraverseArrayWithIndexG[[]A, []B](f) } diff --git a/v2/either/bind.go b/v2/either/bind.go index 4daad87..3ec7ca8 100644 --- a/v2/either/bind.go +++ b/v2/either/bind.go @@ -28,6 +28,8 @@ import ( // // type State struct { x, y int } // result := either.Do[error](State{}) +// +//go:inline func Do[E, S any]( empty S, ) Either[E, S] { @@ -51,6 +53,8 @@ func Do[E, S any]( // }, // ), // ) +// +//go:inline func Bind[E, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) Either[E, T], @@ -78,6 +82,8 @@ func Bind[E, S1, S2, T any]( // func(s State) int { return 32 }, // ), // ) // Right(State{value: 42}) +// +//go:inline func Let[E, S1, S2, T any]( key func(T) func(S1) S2, f func(S1) T, @@ -103,6 +109,8 @@ func Let[E, S1, S2, T any]( // "Alice", // ), // ) // Right(State{name: "Alice"}) +// +//go:inline func LetTo[E, S1, S2, T any]( key func(T) func(S1) S2, b T, @@ -124,6 +132,8 @@ func LetTo[E, S1, S2, T any]( // either.Right[error](42), // either.BindTo(func(v int) State { return State{value: v} }), // ) // Right(State{value: 42}) +// +//go:inline func BindTo[E, S1, T any]( setter func(T) S1, ) func(Either[E, T]) Either[E, S1] { @@ -148,6 +158,8 @@ func BindTo[E, S1, T any]( // either.Right[error](32), // ), // ) // Right(State{x: 10, y: 32}) +// +//go:inline func ApS[E, S1, S2, T any]( setter func(T) func(S1) S2, fa Either[E, T], diff --git a/v2/either/record.go b/v2/either/record.go index b94c924..b8459fe 100644 --- a/v2/either/record.go +++ b/v2/either/record.go @@ -33,6 +33,8 @@ import ( // } // result := either.TraverseRecordG[map[string]string, map[string]int](parse)(map[string]string{"a": "1", "b": "2"}) // // result is Right(map[string]int{"a": 1, "b": 2}) +// +//go:inline func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B any](f func(A) Either[E, B]) func(GA) Either[E, GB] { return RR.Traverse[GA]( Of[E, GB], @@ -54,6 +56,8 @@ func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B any](f func // } // result := either.TraverseRecord[string](parse)(map[string]string{"a": "1", "b": "2"}) // // result is Right(map[string]int{"a": 1, "b": 2}) +// +//go:inline func TraverseRecord[K comparable, E, A, B any](f func(A) Either[E, B]) func(map[K]A) Either[E, map[K]B] { return TraverseRecordG[map[K]A, map[K]B](f) } @@ -73,6 +77,8 @@ func TraverseRecord[K comparable, E, A, B any](f func(A) Either[E, B]) func(map[ // } // result := either.TraverseRecordWithIndexG[map[string]string, map[string]string](validate)(map[string]string{"a": "1"}) // // result is Right(map[string]string{"a": "a:1"}) +// +//go:inline func TraverseRecordWithIndexG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B any](f func(K, A) Either[E, B]) func(GA) Either[E, GB] { return RR.TraverseWithIndex[GA]( Of[E, GB], @@ -96,10 +102,13 @@ func TraverseRecordWithIndexG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B an // } // result := either.TraverseRecordWithIndex[string](validate)(map[string]string{"a": "1"}) // // result is Right(map[string]string{"a": "a:1"}) +// +//go:inline func TraverseRecordWithIndex[K comparable, E, A, B any](f func(K, A) Either[E, B]) func(map[K]A) Either[E, map[K]B] { return TraverseRecordWithIndexG[map[K]A, map[K]B](f) } +//go:inline func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Either[E, A], K comparable, E, A any](ma GOA) Either[E, GA] { return TraverseRecordG[GOA, GA](F.Identity[Either[E, A]])(ma) } @@ -116,6 +125,8 @@ func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Either[E, A], K comparable, E, A an // } // result := either.SequenceRecord(eithers) // // result is Right(map[string]int{"a": 1, "b": 2}) +// +//go:inline func SequenceRecord[K comparable, E, A any](ma map[K]Either[E, A]) Either[E, map[K]A] { return SequenceRecordG[map[K]A](ma) } @@ -158,6 +169,8 @@ func CompactRecordG[M1 ~map[K]Either[E, A], M2 ~map[K]A, K comparable, E, A any] // } // result := either.CompactRecord(eithers) // // result is map[string]int{"a": 1, "c": 3} +// +//go:inline func CompactRecord[K comparable, E, A any](m map[K]Either[E, A]) map[K]A { return CompactRecordG[map[K]Either[E, A], map[K]A](m) } diff --git a/v2/either/semigroup.go b/v2/either/semigroup.go index bd0c62b..7510258 100644 --- a/v2/either/semigroup.go +++ b/v2/either/semigroup.go @@ -29,6 +29,8 @@ import ( // // result is Right(42) // result2 := sg.Concat(either.Right[error](1), either.Right[error](2)) // // result2 is Right(1) - first Right wins +// +//go:inline func AltSemigroup[E, A any]() S.Semigroup[Either[E, A]] { return S.AltSemigroup( MonadAlt[E, A], diff --git a/v2/internal/array/array.go b/v2/internal/array/array.go index 8e0690d..9841093 100644 --- a/v2/internal/array/array.go +++ b/v2/internal/array/array.go @@ -17,10 +17,52 @@ package array func Slice[GA ~[]A, A any](low, high int) func(as GA) GA { return func(as GA) GA { + length := len(as) + + // Handle negative indices - count backward from the end + if low < 0 { + low = max(length+low, 0) + } + if high < 0 { + high = max(length+high, 0) + } + + if low > length { + return Empty[GA, A]() + } + + // End index > array length: slice to the end + if high > length { + high = length + } + + // Start >= end: return empty array + if low >= high { + return Empty[GA, A]() + } + return as[low:high] } } +func SliceRight[GA ~[]A, A any](start int) func(as GA) GA { + return func(as GA) GA { + length := len(as) + + // Handle negative indices - count backward from the end + if start < 0 { + start = max(length+start, 0) + } + + // Start index > array length: return empty array + if start > length { + return Empty[GA, A]() + } + + return as[start:] + } +} + func IsEmpty[GA ~[]A, A any](as GA) bool { return len(as) == 0 }