Repository: duke-git/lancet

Issue:
Slice's RightPadding and LeftPadding have different semantics
Hi @duke-git ,

I noticed that the functions `RightPadding` and `LeftPadding` in the `Slice` package have distinct semantics, leading to possible confusion during usage.  

* The `RightPadding` function __modifies the slice in place__, thus modifying the underlying slice given by the caller.
* The `LeftPadding` function __returns a modified copy of the slice__, thus preserving the original slice by the caller.  

See code snippet below:

```go
// RightPadding adds padding to the right end of a slice.
// Play: https://go.dev/play/p/0_2rlLEMBXL
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
	if paddingLength == 0 {
		return slice
	}
	for i := 0; i < paddingLength; i++ {
		slice = append(slice, paddingValue)
	}
	return slice
}

// LeftPadding adds padding to the left begin of a slice.
// Play: https://go.dev/play/p/jlQVoelLl2k
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
	if paddingLength == 0 {
		return slice
	}

	paddedSlice := make([]T, len(slice)+paddingLength)
	i := 0
	for ; i < paddingLength; i++ {
		paddedSlice[i] = paddingValue
	}
	for j := 0; j < len(slice); j++ {
		paddedSlice[i] = slice[j]
		i++
	}

	return paddedSlice
}
```
Although not wrong in behavior, these functions are too closely related, and a consistent strategy would be more intuitive to users.

I am glad to help fixing one of the functions accordingly. The question however is:  __Which strategy would you like to keep?  (a) Modify the slice in place, or (b) Return a modified copy of the slice, preserving the original slice intact? __.  I would suggest the option (b), but it is your call as the library designer.

Please, let me know your preference and I can submit a pull request accordingly.

Cheers,

Idichekop
 

Discussion:
@duke-git: Hi @idichekop,

Thank you for catching this inconsistency and taking the time to flag it! You’re right that having closely related functions like RightPadding and LeftPadding use different modification strategies can create confusion for users, and aligning them will make the slice package much more intuitive.

After reviewing your analysis and considering the usability of the package, I fully agree with your suggestion to standardize on option (b): returning a modified copy while preserving the original slice. This approach is safer for users, as it avoids unexpected side effects from in-place modifications—especially important in scenarios where the original slice might be referenced elsewhere in their code.

Please feel free to submit a pull request updating RightPadding to match LeftPadding’s behavior. Let me know if you need any clarification or have questions while working on it—I’m happy to help!@duke-git: Hi @idichekop , I'm not sure if you have any interest in maintaining the lancet library. If you do, I'd be happy to set you up as github collaborator for the lancet repository, who have permissions to review and merge code.@idichekop: > Hi [@idichekop](https://github.com/idichekop) , I'm not sure if you have any interest in maintaining the lancet library. If you do, I'd be happy to set you up as github collaborator for the lancet repository, who have permissions to review and merge code.

Hi @duke-git , I really feel honored.  I can help you on that, for sure!

I liked the lancet library very much in many ways, also how it is documented and well cared.  So, I started looking it in deep to adopt as a base lib in my projects.  I am not there yet, as I still have to look deeper in many other packages.  I like the functionality you collected with the time.  Here and there, I see some snippets that can be modernized or made more consistent (like the ones I signaled so far).  If you allow me to help you on modernizing these parts, it will help me in the future as well...

So, yes, if you feel I can give a hand on reviewing others codes and looking a bit deeper, I will be happy to join.

ps.:  Excuses the delay on responding. I am currently on vacations and taking some time away from the keyboard ;).@duke-git: Hi @idichekop 

Thanks for your enthusiastic response. It’s fantastic to have someone who appreciates the library’s ethos and is eager to help improve it. I’ve invited you as a collaborator on the lancet repo.

Hope you have a wonderful time and enjoy the rest of your vacation.

Best.@idichekop: Solved in #322

Solution patch:
diff --git a/slice/slice.go b/slice/slice.go
index 1264c0c..c477989 100644
--- a/slice/slice.go
+++ b/slice/slice.go
@@ -507,19 +507,17 @@ func flattenRecursive(value reflect.Value, result reflect.Value) reflect.Value {
 }
 
 // ForEach iterates over elements of slice and invokes function for each element.
-// Play: https://go.dev/play/p/DrPaa4YsHRF
 func ForEach[T any](slice []T, iteratee func(index int, item T)) {
-	for i := 0; i < len(slice); i++ {
-		iteratee(i, slice[i])
+	for idx, elem := range slice {
+		iteratee(idx, elem)
 	}
 }
 
 // ForEachWithBreak iterates over elements of slice and invokes function for each element,
-// when iteratee return false, will break the for each loop.
-// Play: https://go.dev/play/p/qScs39f3D9W
+// when function return false, will break the for each loop.
 func ForEachWithBreak[T any](slice []T, iteratee func(index int, item T) bool) {
-	for i := 0; i < len(slice); i++ {
-		if !iteratee(i, slice[i]) {
+	for idx, elem := range slice {
+		if !iteratee(idx, elem) {
 			break
 		}
 	}
@@ -1465,36 +1463,28 @@ func Random[T any](slice []T) (val T, idx int) {
 	return slice[idx], idx
 }
 
-// RightPadding adds padding to the right end of a slice.
+// RightPadding returns a copy of the slice padding the given value to the right end of a slice.
+// If paddingLength is zero or less, the function returns a copy of the slice.
 // Play: https://go.dev/play/p/0_2rlLEMBXL
 func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
-	if paddingLength == 0 {
-		return slice
+	suffix := []T{}
+	if paddingLength > 0 {
+		suffix = repeat([]T{paddingValue}, paddingLength)
 	}
-	for i := 0; i < paddingLength; i++ {
-		slice = append(slice, paddingValue)
-	}
-	return slice
+	padded := concat(slice, suffix)
+	return padded
 }
 
-// LeftPadding adds padding to the left begin of a slice.
+// LeftPadding returns a copy of the slice padding the given value to the left begin of a slice.
+// If paddingLength is zero or less, the function returns a copy of the slice.
 // Play: https://go.dev/play/p/jlQVoelLl2k
 func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
-	if paddingLength == 0 {
-		return slice
+	prefix := []T{}
+	if paddingLength > 0 {
+		prefix = repeat([]T{paddingValue}, paddingLength)
 	}
-
-	paddedSlice := make([]T, len(slice)+paddingLength)
-	i := 0
-	for ; i < paddingLength; i++ {
-		paddedSlice[i] = paddingValue
-	}
-	for j := 0; j < len(slice); j++ {
-		paddedSlice[i] = slice[j]
-		i++
-	}
-
-	return paddedSlice
+	padded := concat(prefix, slice)
+	return padded
 }
 
 // Frequency counts the frequency of each element in the slice.
diff --git a/slice/slice_internal.go b/slice/slice_internal.go
index f6c275e..201fa53 100644
--- a/slice/slice_internal.go
+++ b/slice/slice_internal.go
@@ -2,6 +2,7 @@ package slice
 
 import (
 	"fmt"
+	"math/bits"
 	"reflect"
 
 	"golang.org/x/exp/constraints"
@@ -96,3 +97,71 @@ func partitionAnySlice[T any](slice []T, lowIndex, highIndex int, less func(a, b
 func swap[T any](slice []T, i, j int) {
 	slice[i], slice[j] = slice[j], slice[i]
 }
+
+// `repeat` returns a new slice that repeats the provided slice the given number of
+// times. The result has length and capacity (len(x) * count). The result is never nil.
+// Repeat panics if count is negative or if the result of (len(x) * count)  overflows.
+//
+// repeat has been provided in the standard lib within the package `slices` under the
+// name Repeat since GO version 1.21 onwards. As lancet commits to compatibility with GO
+// 1.18 onwards, we implement the functionality as an internal function.
+func repeat[S ~[]E, E any](x S, count int) S {
+	if count < 0 {
+		panic("count cannot be negative")
+	}
+
+	const maxInt = ^uint(0) >> 1
+	hi, lo := bits.Mul(uint(len(x)), uint(count))
+	if hi > 0 || lo > maxInt {
+		panic("the result of (len(x) * count) overflows")
+	}
+
+	newslice := make(S, int(lo)) // lo = len(x) * count
+	n := copy(newslice, x)
+	for n < len(newslice) {
+		n += copy(newslice[n:], newslice[:n])
+	}
+	return newslice
+}
+
+// concat returns a new slice concatenating the passed in slices.
+//
+// concat has been provided in the standard lib within the package `slices` under the
+// name Concat since GO version 1.21 onwards. As lancet commits to compatibility with GO
+// 1.18 onwards, we implement the functionality as an internal function.
+func concat[S ~[]E, E any](slices ...S) S {
+	size := 0
+	for _, s := range slices {
+		size += len(s)
+		if size < 0 {
+			panic("len out of range")
+		}
+	}
+	// Use Grow, not make, to round up to the size class:
+	// the extra space is otherwise unused and helps
+	// callers that append a few elements to the result.
+	newslice := grow[S](nil, size)
+	for _, s := range slices {
+		newslice = append(newslice, s...)
+	}
+	return newslice
+}
+
+// grow increases the slice's capacity, if necessary, to guarantee space for
+// another n elements. After grow(n), at least n elements can be appended
+// to the slice without another allocation. If n is negative or too large to
+// allocate the memory, grow panics.
+//
+// grow has been provided in the standard lib within the package `slices` under the
+// name Grow since GO version 1.21 onwards. As lancet commits to compatibility with GO
+// 1.18 onwards, we implement the functionality as an internal function.
+func grow[S ~[]E, E any](s S, n int) S {
+	if n < 0 {
+		panic("cannot be negative")
+	}
+	if n -= cap(s) - len(s); n > 0 {
+		// This expression allocates only once.
+		s = append(s[:cap(s)], make([]E, n)...)[:len(s)]
+	}
+	return s
+}



Test patch:
diff --git a/slice/slice_test.go b/slice/slice_test.go
index fbbed93..8fa8976 100644
--- a/slice/slice_test.go
+++ b/slice/slice_test.go
@@ -1756,6 +1756,20 @@ func TestRightPaddingAndLeftPadding(t *testing.T) {
 
 	padded := LeftPadding(RightPadding(nums, 0, 3), 0, 3)
 	assert.Equal([]int{0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0}, padded)
+
+	// Test with negative padding length
+	paddedNegative := LeftPadding(RightPadding(nums, 0, -3), 0, -3)
+	assert.Equal([]int{1, 2, 3, 4, 5}, paddedNegative)
+
+	// Test with empty slice
+	empty := []int{}
+	paddedEmpty := LeftPadding(RightPadding(empty, 0, 3), 0, 3)
+	assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedEmpty)
+
+	// Test with nil
+	nilSlice := []int(nil)
+	paddedNil := LeftPadding(RightPadding(nilSlice, 0, 3), 0, 3)
+	assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedNil)
 }
 
 func TestUniqueByConcurrent(t *testing.T) {