From 4e1579cf4c920f94cc93bfe8c726ad8f1a285966 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 24 Sep 2024 16:15:46 +0900 Subject: [PATCH] fn: add uncons, unsnoc and its component projections --- fn/slice.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ fn/slice_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/fn/slice.go b/fn/slice.go index 22f3fa6f536..3b25b32a1ae 100644 --- a/fn/slice.go +++ b/fn/slice.go @@ -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)) +} diff --git a/fn/slice_test.go b/fn/slice_test.go index b91f35e48b6..86c870ff8e6 100644 --- a/fn/slice_test.go +++ b/fn/slice_test.go @@ -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{})) +}