mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-19 12:01:27 +02:00
fn: add compound slice functions
This commit is contained in:
94
fn/slice.go
94
fn/slice.go
@@ -65,6 +65,34 @@ func Filter[A any](pred Pred[A], s []A) []A {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterMap takes a function argument that optionally produces a value and
|
||||||
|
// returns a slice of the 'Some' return values.
|
||||||
|
func FilterMap[A, B any](as []A, f func(A) Option[B]) []B {
|
||||||
|
var bs []B
|
||||||
|
|
||||||
|
for _, a := range as {
|
||||||
|
f(a).WhenSome(func(b B) {
|
||||||
|
bs = append(bs, b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimNones takes a slice of Option values and returns a slice of the Some
|
||||||
|
// values in it.
|
||||||
|
func TrimNones[A any](as []Option[A]) []A {
|
||||||
|
var somes []A
|
||||||
|
|
||||||
|
for _, a := range as {
|
||||||
|
a.WhenSome(func(b A) {
|
||||||
|
somes = append(somes, b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return somes
|
||||||
|
}
|
||||||
|
|
||||||
// Foldl iterates through all members of the slice left to right and reduces
|
// Foldl iterates through all members of the slice left to right and reduces
|
||||||
// them pairwise with an accumulator value that is seeded with the seed value in
|
// them pairwise with an accumulator value that is seeded with the seed value in
|
||||||
// the argument.
|
// the argument.
|
||||||
@@ -318,3 +346,69 @@ func Unsnoc[A any](items []A) Option[T2[[]A, A]] {
|
|||||||
func Len[A any](items []A) uint {
|
func Len[A any](items []A) uint {
|
||||||
return uint(len(items))
|
return uint(len(items))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectOptions collects a list of Options into a single Option of the list of
|
||||||
|
// Some values in it. If there are any Nones present it will return None.
|
||||||
|
func CollectOptions[A any](options []Option[A]) Option[[]A] {
|
||||||
|
// We intentionally do a separate None checking pass here to avoid
|
||||||
|
// allocating a new slice for the values until we're sure we need to.
|
||||||
|
for _, r := range options {
|
||||||
|
if r.IsNone() {
|
||||||
|
return None[[]A]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we're sure we have no Nones, we can just do an unchecked
|
||||||
|
// index into the some value of the option.
|
||||||
|
return Some(Map(func(o Option[A]) A { return o.some }, options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectResults collects a list of Results into a single Result of the list of
|
||||||
|
// Ok values in it. If there are any errors present it will return the first
|
||||||
|
// error encountered.
|
||||||
|
func CollectResults[A any](results []Result[A]) Result[[]A] {
|
||||||
|
// We intentionally do a separate error checking pass here to avoid
|
||||||
|
// allocating a new slice for the results until we're sure we need to.
|
||||||
|
for _, r := range results {
|
||||||
|
if r.IsErr() {
|
||||||
|
return Err[[]A](r.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we're sure we have no errors, we can just do an unchecked
|
||||||
|
// index into the left side of the result.
|
||||||
|
return Ok(Map(func(r Result[A]) A { return r.left }, results))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraverseOption traverses a slice of A values, applying the provided
|
||||||
|
// function to each, collecting the results into an Option of a slice of B
|
||||||
|
// values. If any of the results are None, the entire result is None.
|
||||||
|
func TraverseOption[A, B any](as []A, f func(A) Option[B]) Option[[]B] {
|
||||||
|
var bs []B
|
||||||
|
for _, a := range as {
|
||||||
|
b := f(a)
|
||||||
|
if b.IsNone() {
|
||||||
|
return None[[]B]()
|
||||||
|
}
|
||||||
|
bs = append(bs, b.some)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraverseResult traverses a slice of A values, applying the provided
|
||||||
|
// function to each, collecting the results into a Result of a slice of B
|
||||||
|
// values. If any of the results are Err, the entire result is the first
|
||||||
|
// error encountered.
|
||||||
|
func TraverseResult[A, B any](as []A, f func(A) Result[B]) Result[[]B] {
|
||||||
|
var bs []B
|
||||||
|
for _, a := range as {
|
||||||
|
b := f(a)
|
||||||
|
if b.IsErr() {
|
||||||
|
return Err[[]B](b.right)
|
||||||
|
}
|
||||||
|
bs = append(bs, b.left)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(bs)
|
||||||
|
}
|
||||||
|
252
fn/slice_test.go
252
fn/slice_test.go
@@ -2,6 +2,7 @@ package fn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/quick"
|
"testing/quick"
|
||||||
@@ -413,6 +414,122 @@ func TestPropTailDecrementsLength(t *testing.T) {
|
|||||||
require.NoError(t, quick.Check(f, nil))
|
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{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropAlwaysNoneEmptyFilterMap ensures the property that if we were to
|
||||||
|
// always return none from our filter function then we would end up with an
|
||||||
|
// empty slice.
|
||||||
|
func TestPropAlwaysNoneEmptyFilterMap(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
filtered := FilterMap(s, Const[Option[int], int](None[int]()))
|
||||||
|
return len(filtered) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropFilterMapSomeIdentity ensures that if the filter function is a
|
||||||
|
// trivial lift into Option space, then we will get back the original slice.
|
||||||
|
func TestPropFilterMapSomeIdentity(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
filtered := FilterMap(s, Some[int])
|
||||||
|
return slices.Equal(s, filtered)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropFilterMapCantGrow ensures that regardless of the filter functions
|
||||||
|
// return values, we will never end up with a slice larger than the original.
|
||||||
|
func TestPropFilterMapCantGrow(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
filterFunc := func(i int) Option[int] {
|
||||||
|
if rand.Int()%2 == 0 {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(i + rand.Int())
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(FilterMap(s, filterFunc)) <= len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropFilterMapBisectIdentity ensures that the concatenation of the
|
||||||
|
// FilterMaps is the same as the FilterMap of the concatenation.
|
||||||
|
func TestPropFilterMapBisectIdentity(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
sz := len(s)
|
||||||
|
first := s[0 : sz/2]
|
||||||
|
second := s[sz/2 : sz]
|
||||||
|
|
||||||
|
filterFunc := func(i int) Option[int] {
|
||||||
|
if i%2 == 0 {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstFiltered := FilterMap(first, filterFunc)
|
||||||
|
secondFiltered := FilterMap(second, filterFunc)
|
||||||
|
allFiltered := FilterMap(s, filterFunc)
|
||||||
|
reassembled := slices.Concat(firstFiltered, secondFiltered)
|
||||||
|
|
||||||
|
return slices.Equal(allFiltered, reassembled)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTraverseOkIdentity ensures that trivially lifting the elements of a slice
|
||||||
|
// via the Ok function during a Traverse is equivalent to just lifting the
|
||||||
|
// entire slice via the Ok function.
|
||||||
|
func TestPropTraverseOkIdentity(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
traversed := TraverseResult(s, Ok[int])
|
||||||
|
|
||||||
|
traversedOk := traversed.UnwrapOrFail(t)
|
||||||
|
|
||||||
|
return slices.Equal(s, traversedOk)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropTraverseSingleErrEjection ensures that if the traverse function
|
||||||
|
// returns even a single error, then the entire Traverse will error.
|
||||||
|
func TestPropTraverseSingleErrEjection(t *testing.T) {
|
||||||
|
f := func(s []int, errIdx uint8) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut := int(errIdx) % len(s)
|
||||||
|
f := func(i int) Result[int] {
|
||||||
|
if errIdxMut == 0 {
|
||||||
|
return Errf[int]("err")
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut--
|
||||||
|
|
||||||
|
return Ok(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TraverseResult(s, f).IsErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
func TestPropInitDecrementsLength(t *testing.T) {
|
func TestPropInitDecrementsLength(t *testing.T) {
|
||||||
f := func(s []uint8) bool {
|
f := func(s []uint8) bool {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
@@ -425,10 +542,137 @@ func TestPropInitDecrementsLength(t *testing.T) {
|
|||||||
require.NoError(t, quick.Check(f, nil))
|
require.NoError(t, quick.Check(f, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSingletonTailIsEmpty(t *testing.T) {
|
// TestPropTrimNonesEqualsFilterMapIden checks that if we use the Iden
|
||||||
require.Equal(t, Tail([]int{1}), Some([]int{}))
|
// function when calling FilterMap on a slice of Options that we get the same
|
||||||
|
// result as we would if we called TrimNones on it.
|
||||||
|
func TestPropTrimNonesEqualsFilterMapIden(t *testing.T) {
|
||||||
|
f := func(s []uint8) bool {
|
||||||
|
withNones := make([]Option[uint8], len(s))
|
||||||
|
for i, x := range s {
|
||||||
|
if x%3 == 0 {
|
||||||
|
withNones[i] = None[uint8]()
|
||||||
|
} else {
|
||||||
|
withNones[i] = Some(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Equal(
|
||||||
|
FilterMap(withNones, Iden[Option[uint8]]),
|
||||||
|
TrimNones(withNones),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSingletonInitIsEmpty(t *testing.T) {
|
// TestPropCollectResultsSingleErrEjection ensures that if there is even a
|
||||||
require.Equal(t, Init([]int{1}), Some([]int{}))
|
// single error in the batch, then CollectResults will return an error.
|
||||||
|
func TestPropCollectResultsSingleErrEjection(t *testing.T) {
|
||||||
|
f := func(s []int, errIdx uint8) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut := int(errIdx) % len(s)
|
||||||
|
f := func(i int) Result[int] {
|
||||||
|
if errIdxMut == 0 {
|
||||||
|
return Errf[int]("err")
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut--
|
||||||
|
|
||||||
|
return Ok(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return CollectResults(Map(f, s)).IsErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropCollectResultsNoErrUnwrap ensures that if there are no errors in the
|
||||||
|
// results then we end up with unwrapping all of the Results in the slice.
|
||||||
|
func TestPropCollectResultsNoErrUnwrap(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
res := CollectResults(Map(Ok[int], s))
|
||||||
|
return !res.isRight && slices.Equal(res.left, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropTraverseSomeIdentity ensures that trivially lifting the elements of a
|
||||||
|
// slice via the Some function during a Traverse is equivalent to just lifting
|
||||||
|
// the entire slice via the Some function.
|
||||||
|
func TestPropTraverseSomeIdentity(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
traversed := TraverseOption(s, Some[int])
|
||||||
|
|
||||||
|
traversedSome := traversed.UnwrapOrFail(t)
|
||||||
|
|
||||||
|
return slices.Equal(s, traversedSome)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTraverseSingleNoneEjection ensures that if the traverse function returns
|
||||||
|
// even a single None, then the entire Traverse will return None.
|
||||||
|
func TestTraverseSingleNoneEjection(t *testing.T) {
|
||||||
|
f := func(s []int, errIdx uint8) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut := int(errIdx) % len(s)
|
||||||
|
f := func(i int) Option[int] {
|
||||||
|
if errIdxMut == 0 {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut--
|
||||||
|
|
||||||
|
return Some(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TraverseOption(s, f).IsNone()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropCollectOptionsSingleNoneEjection ensures that if there is even a
|
||||||
|
// single None in the batch, then CollectOptions will return a None.
|
||||||
|
func TestPropCollectOptionsSingleNoneEjection(t *testing.T) {
|
||||||
|
f := func(s []int, errIdx uint8) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut := int(errIdx) % len(s)
|
||||||
|
f := func(i int) Option[int] {
|
||||||
|
if errIdxMut == 0 {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
errIdxMut--
|
||||||
|
|
||||||
|
return Some(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return CollectOptions(Map(f, s)).IsNone()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPropCollectOptionsNoNoneUnwrap ensures that if there are no nones in the
|
||||||
|
// options then we end up with unwrapping all of the Options in the slice.
|
||||||
|
func TestPropCollectOptionsNoNoneUnwrap(t *testing.T) {
|
||||||
|
f := func(s []int) bool {
|
||||||
|
res := CollectOptions(Map(Some[int], s))
|
||||||
|
return res.isSome && slices.Equal(res.some, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, quick.Check(f, nil))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user