Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions fn/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,63 @@ func ForEachConc[A, B any](f func(A) B,

return bs
}

// Head returns the first element of the slice, assuming it is non-empty.
func Head[A any](items []A) Option[A] {
if len(items) == 0 {
return None[A]()
}

return Some(items[0])
}

// Tail returns the slice without the first element, assuming the slice is not
// empty. Note this makes a copy of the slice.
func Tail[A any](items []A) Option[[]A] {
if len(items) == 0 {
return None[[]A]()
}

res := make([]A, len(items)-1)
copy(res, items[1:])

return Some(res)
}

// Init returns the slice without the last element, assuming the slice is not
// empty. Note this makes a copy of the slice.
func Init[A any](items []A) Option[[]A] {
if len(items) == 0 {
return None[[]A]()
}

res := make([]A, len(items)-1)
copy(res, items[0:len(items)-1])

return Some(res)
}

// Last returns the last element of the slice, assuming it is non-empty.
func Last[A any](items []A) Option[A] {
if len(items) == 0 {
return None[A]()
}

return Some(items[len(items)-1])
}

// Uncons splits a slice into a pair of its Head and Tail.
func Uncons[A any](items []A) Option[T2[A, []A]] {
return LiftA2Option(NewT2[A, []A])(Head(items), Tail(items))
}

// Unsnoc splits a slice into a pair of its Init and Last.
func Unsnoc[A any](items []A) Option[T2[[]A, A]] {
return LiftA2Option(NewT2[[]A, A])(Init(items), Last(items))
}

// Len is the len function that is defined in a way that makes it usable in
// higher-order contexts.
func Len[A any](items []A) uint {
return uint(len(items))
}
58 changes: 58 additions & 0 deletions fn/slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,61 @@ func TestPropFindIdxFindIdentity(t *testing.T) {
t.Fatal(err)
}
}

func TestPropLastTailIsLast(t *testing.T) {
f := func(s []uint8) bool {
// We exclude the singleton case because the Tail is empty.
if len(s) <= 1 {
return true
}

return Last(s) == ChainOption(Last[uint8])(Tail(s))
}

require.NoError(t, quick.Check(f, nil))
}

func TestPropHeadInitIsHead(t *testing.T) {
f := func(s []uint8) bool {
// We exclude the singleton case because the Init is empty.
if len(s) <= 1 {
return true
}

return Head(s) == ChainOption(Head[uint8])(Init(s))
}

require.NoError(t, quick.Check(f, nil))
}

func TestPropTailDecrementsLength(t *testing.T) {
f := func(s []uint8) bool {
if len(s) == 0 {
return true
}

return Some(Len(s)-1) == MapOption(Len[uint8])(Tail(s))
}

require.NoError(t, quick.Check(f, nil))
}

func TestPropInitDecrementsLength(t *testing.T) {
f := func(s []uint8) bool {
if len(s) == 0 {
return true
}

return Some(Len(s)-1) == MapOption(Len[uint8])(Init(s))
}

require.NoError(t, quick.Check(f, nil))
}

func TestSingletonTailIsEmpty(t *testing.T) {
require.Equal(t, Tail([]int{1}), Some([]int{}))
}

func TestSingletonInitIsEmpty(t *testing.T) {
require.Equal(t, Init([]int{1}), Some([]int{}))
}
Loading