Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2024-07-12 17:30:15 -07:00
commit 09b38aaa01
14 files changed
+1586 -65

No files matched your search

+3 -5
View File
@@ -2,8 +2,6 @@ package fn
import (
"sync"
"github.com/lightninglabs/neutrino/cache/lru"
)
// ConcurrentQueue is a typed concurrent-safe FIFO queue with unbounded
@@ -17,7 +15,7 @@ type ConcurrentQueue[T any] struct {
chanIn chan T
chanOut chan T
overflow *lru.List[T]
overflow *List[T]
wg sync.WaitGroup
quit chan struct{}
@@ -25,13 +23,13 @@ type ConcurrentQueue[T any] struct {
// NewConcurrentQueue constructs a ConcurrentQueue. The bufferSize parameter is
// the capacity of the output channel. When the size of the queue is below this
// threshold, pushes do n[?12;4$yot incur the overhead of the less efficient overflow
// threshold, pushes do not incur the overhead of the less efficient overflow
// structure.
func NewConcurrentQueue[T any](bufferSize int) *ConcurrentQueue[T] {
return &ConcurrentQueue[T]{
chanIn: make(chan T),
chanOut: make(chan T, bufferSize),
overflow: lru.NewList[T](),
overflow: NewList[T](),
quit: make(chan struct{}),
}
}
+102 -13
View File
@@ -2,47 +2,136 @@ package fn
// Either is a type that can be either left or right.
type Either[L any, R any] struct {
left Option[L]
right Option[R]
isRight bool
left L
right R
}
// NewLeft returns an Either with a left value.
func NewLeft[L any, R any](l L) Either[L, R] {
return Either[L, R]{left: Some(l), right: None[R]()}
return Either[L, R]{left: l}
}
// NewRight returns an Either with a right value.
func NewRight[L any, R any](r R) Either[L, R] {
return Either[L, R]{left: None[L](), right: Some(r)}
return Either[L, R]{isRight: true, right: r}
}
// ElimEither is the universal Either eliminator. It can be used to safely
// handle all possible values inside the Either by supplying two continuations,
// one for each side of the Either.
func ElimEither[L, R, O any](f func(L) O, g func(R) O, e Either[L, R]) O {
if !e.isRight {
return f(e.left)
}
return g(e.right)
}
// WhenLeft executes the given function if the Either is left.
func (e Either[L, R]) WhenLeft(f func(L)) {
e.left.WhenSome(f)
if !e.isRight {
f(e.left)
}
}
// WhenRight executes the given function if the Either is right.
func (e Either[L, R]) WhenRight(f func(R)) {
e.right.WhenSome(f)
if e.isRight {
f(e.right)
}
}
// IsLeft returns true if the Either is left.
func (e Either[L, R]) IsLeft() bool {
return e.left.IsSome()
return !e.isRight
}
// IsRight returns true if the Either is right.
func (e Either[L, R]) IsRight() bool {
return e.right.IsSome()
return e.isRight
}
// LeftToOption converts a Left value to an Option, returning None if the inner
// Either value is a Right value.
func (e Either[L, R]) LeftToOption() Option[L] {
if e.isRight {
return None[L]()
}
return Some(e.left)
}
// RightToOption converts a Right value to an Option, returning None if the
// inner Either value is a Left value.
func (e Either[L, R]) RightToOption() Option[R] {
if !e.isRight {
return None[R]()
}
return Some(e.right)
}
// UnwrapLeftOr will extract the Left value from the Either if it is present
// returning the supplied default if it is not.
func (e Either[L, R]) UnwrapLeftOr(l L) L {
if e.isRight {
return l
}
return e.left
}
// UnwrapRightOr will extract the Right value from the Either if it is present
// returning the supplied default if it is not.
func (e Either[L, R]) UnwrapRightOr(r R) R {
if !e.isRight {
return r
}
return e.right
}
// Swap reverses the type argument order. This can be useful as an adapter
// between APIs.
func (e Either[L, R]) Swap() Either[R, L] {
return Either[R, L]{
isRight: !e.isRight,
left: e.right,
right: e.left,
}
}
// MapLeft maps the left value of the Either to a new value.
func MapLeft[L any, R any, O any](f func(L) O) func(Either[L, R]) Option[O] {
return func(e Either[L, R]) Option[O] {
if e.IsLeft() {
return MapOption(f)(e.left)
func MapLeft[L, R, O any](f func(L) O) func(Either[L, R]) Either[O, R] {
return func(e Either[L, R]) Either[O, R] {
if !e.isRight {
return Either[O, R]{
isRight: false,
left: f(e.left),
}
}
return None[O]()
return Either[O, R]{
isRight: true,
right: e.right,
}
}
}
// MapRight maps the right value of the Either to a new value.
func MapRight[L, R, O any](f func(R) O) func(Either[L, R]) Either[L, O] {
return func(e Either[L, R]) Either[L, O] {
if e.isRight {
return Either[L, O]{
isRight: true,
right: f(e.right),
}
}
return Either[L, O]{
isRight: false,
left: e.left,
}
}
}
+139
View File
@@ -0,0 +1,139 @@
package fn
import (
"testing"
"testing/quick"
)
func TestPropConstructorEliminatorDuality(t *testing.T) {
f := func(i int, s string, isRight bool) bool {
Len := func(s string) int { return len(s) } // smh
if isRight {
v := ElimEither(
Iden[int],
Len,
NewRight[int, string](s),
)
return v == Len(s)
}
v := ElimEither(
Iden[int],
Len,
NewLeft[int, string](i),
)
return v == i
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
func TestPropWhenClauseExclusivity(t *testing.T) {
f := func(i int, isRight bool) bool {
var e Either[int, int]
if isRight {
e = NewRight[int, int](i)
} else {
e = NewLeft[int, int](i)
}
z := 0
e.WhenLeft(func(x int) { z += x })
e.WhenRight(func(x int) { z += x })
return z != 2*i && e.IsLeft() != e.IsRight()
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
func TestPropSwapEitherSelfInverting(t *testing.T) {
f := func(i int, s string, isRight bool) bool {
var e Either[int, string]
if isRight {
e = NewRight[int, string](s)
} else {
e = NewLeft[int, string](i)
}
return e.Swap().Swap() == e
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
func TestPropMapLeftIdentity(t *testing.T) {
f := func(i int, s string, isRight bool) bool {
var e Either[int, string]
if isRight {
e = NewRight[int, string](s)
} else {
e = NewLeft[int, string](i)
}
return MapLeft[int, string, int](Iden[int])(e) == e
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
func TestPropMapRightIdentity(t *testing.T) {
f := func(i int, s string, isRight bool) bool {
var e Either[int, string]
if isRight {
e = NewRight[int, string](s)
} else {
e = NewLeft[int, string](i)
}
return MapRight[int, string, string](Iden[string])(e) == e
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
func TestPropToOptionIdentities(t *testing.T) {
f := func(i int, s string, isRight bool) bool {
var e Either[int, string]
if isRight {
e = NewRight[int, string](s)
r2O := e.RightToOption() == Some(s)
o2R := e == OptionToRight[string, int, string](
Some(s), i,
)
l2O := e.LeftToOption() == None[int]()
return r2O && o2R && l2O
} else {
e = NewLeft[int, string](i)
l2O := e.LeftToOption() == Some(i)
o2L := e == OptionToLeft[int, int](Some(i), s)
r2O := e.RightToOption() == None[string]()
return l2O && o2L && r2O
}
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
func TestPropUnwrapIdentities(t *testing.T) {
f := func(i int, s string, isRight bool) bool {
var e Either[int, string]
if isRight {
e = NewRight[int, string](s)
return e.UnwrapRightOr("") == s &&
e.UnwrapLeftOr(0) == 0
} else {
e = NewLeft[int, string](i)
return e.UnwrapLeftOr(0) == i &&
e.UnwrapRightOr("") == ""
}
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
+46
View File
@@ -0,0 +1,46 @@
package fn
// Unit is a type alias for the empty struct to make it a bit less noisy to
// communicate the informationaless type.
type Unit = struct{}
// Comp is left to right function composition. Comp(f, g)(x) == g(f(x)). This
// can make it easier to create on the fly closures that we may use as
// arguments to other functions defined in this package (or otherwise).
func Comp[A, B, C any](f func(A) B, g func(B) C) func(A) C {
return func(a A) C {
return g(f(a))
}
}
// Iden is the left and right identity of Comp. It is a function that simply
// returns its argument. The utility of this function is only apparent in
// conjunction with other functions in this package.
func Iden[A any](a A) A {
return a
}
// Const is a function that accepts an argument and returns a function that
// always returns that value irrespective of the returned function's argument.
// This is also quite useful in conjunction with higher order functions.
func Const[A, B any](a A) func(B) A {
return func(_ B) A {
return a
}
}
// Eq is a curried function that returns true if its eventual two arguments are
// equal.
func Eq[A comparable](x A) func(A) bool {
return func(y A) bool {
return x == y
}
}
// Neq is a curried function that returns true if its eventual two arguments are
// not equal.
func Neq[A comparable](x A) func(A) bool {
return func(y A) bool {
return x != y
}
}
-17
View File
@@ -1,17 +0,0 @@
package fn
// Reducer represents a function that takes an accumulator and the value, then
// returns a new accumulator.
type Reducer[T, V any] func(accum T, value V) T
// Reduce takes a slice of something, and a reducer, and produces a final
// accumulated value.
func Reduce[T any, V any, S []V](s S, f Reducer[T, V]) T {
var accum T
for _, x := range s {
accum = f(accum, x)
}
return accum
}
+1 -1
View File
@@ -3,9 +3,9 @@ module github.com/lightningnetwork/lnd/fn
go 1.19
require (
github.com/lightninglabs/neutrino/cache v1.1.2
github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b
golang.org/x/sync v0.7.0
)
require (
+2 -2
View File
@@ -1,8 +1,6 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -14,6 +12,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+302
View File
@@ -0,0 +1,302 @@
// Copyright (c) 2009 The Go Authors. All rights reserved.
// Copyright (c) 2024 Lightning Labs and the Lightning Network Developers
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fn
type Node[A any] struct {
// prev is a pointer to the previous node in the List.
prev *Node[A]
// next is a pointer to the next node in the List.
next *Node[A]
// list is the root pointer to the List in which this node is located.
list *List[A]
// Value is the actual data contained within the Node.
Value A
}
// Next returns the next list node or nil.
func (e *Node[A]) Next() *Node[A] {
if e.list == nil {
return nil
}
if e.next == &e.list.root {
return nil
}
return e.next
}
// Prev returns the previous list node or nil.
func (e *Node[A]) Prev() *Node[A] {
if e.list == nil {
return nil
}
if e.prev == &e.list.root {
return nil
}
return e.prev
}
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List[A any] struct {
// root is a sentinal Node to identify the head and tail of the list.
// root.prev is the tail, root.next is the head. For the purposes of
// elegance, the absence of a next or prev node is encoded as the
// address of the root node.
root Node[A]
// len is the current length of the list.
len int
}
// Init intializes or clears the List l.
func (l *List[A]) Init() *List[A] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
// lazyInit lazily initializes a zero List value. It is called by other public
// functions that could feasibly be called on a List that was initialized by the
// raw List[A]{} constructor.
func (l *List[A]) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
// insert inserts n after predecessor, increments l.len, and returns n.
func (l *List[A]) insert(n *Node[A], predecessor *Node[A]) *Node[A] {
// Make n point to correct neighborhood.
n.prev = predecessor
n.next = predecessor.next
// Make neighborhood point to n.
n.prev.next = n
n.next.prev = n
// Make n part of the list.
n.list = l
// Increment list length.
l.len++
return n
}
// insertVal is a convenience wrapper for
// insert(&Node[A]{Value: v}, predecessor).
func (l *List[A]) insertVal(a A, predecessor *Node[A]) *Node[A] {
return l.insert(&Node[A]{Value: a}, predecessor)
}
// move removes n from its current position and inserts it as the successor to
// predecessor.
func (l *List[A]) move(n *Node[A], predecessor *Node[A]) {
if n == predecessor {
return // Can't move after itself.
}
if predecessor.next == n {
return // Nothing to be done.
}
// Bind previous and next to each other.
n.prev.next = n.next
n.next.prev = n.prev
// Make n point to new neighborhood.
n.prev = predecessor
n.next = predecessor.next
// Make new neighborhood point to n.
n.prev.next = n
n.next.prev = n
}
// New returns an initialized List.
func NewList[A any]() *List[A] {
l := List[A]{}
return l.Init()
}
// Len returns the number of elements of List l.
// The complexity is O(1).
func (l *List[A]) Len() int {
return l.len
}
// Front returns the first Node of List l or nil if the list is empty.
func (l *List[A]) Front() *Node[A] {
if l.len == 0 {
return nil
}
return l.root.next
}
// Back returns the last Node of List l or nil if the list is empty.
func (l *List[A]) Back() *Node[A] {
if l.len == 0 {
return nil
}
return l.root.prev
}
// Remove removes Node n from List l if n is an element of List l.
// It returns the Node value e.Value.
// The Node must not be nil.
func (l *List[A]) Remove(n *Node[A]) A {
if n.list == l {
n.prev.next = n.next
n.next.prev = n.prev
l.len--
v := n.Value
// Set all node data to nil to prevent dangling references.
*n = Node[A]{Value: v}
return v
}
return n.Value
}
// PushFront inserts a new Node n with value a at the front of List l and
// returns n.
func (l *List[A]) PushFront(a A) *Node[A] {
l.lazyInit()
return l.insertVal(a, &l.root)
}
// PushBack inserts a new Node n with value a at the back of List l and returns
// n.
func (l *List[A]) PushBack(a A) *Node[A] {
l.lazyInit()
return l.insertVal(a, l.root.prev)
}
// InsertBefore inserts a new Node n with value a immediately before successor
// and returns n. If successor is not an element of l, the list is not
// modified. The successor must not be nil.
func (l *List[A]) InsertBefore(a A, successor *Node[A]) *Node[A] {
if successor == nil {
return l.insertVal(a, &l.root)
}
if successor.list != l {
return nil
}
return l.insertVal(a, successor.prev)
}
// InsertAfter inserts a new Node n with value a immediately after and returns
// e. If predecessor is not an element of l, the list is not modified. The
// predecessor must not be nil.
func (l *List[A]) InsertAfter(a A, predecessor *Node[A]) *Node[A] {
if predecessor == nil {
return l.insertVal(a, l.root.prev)
}
if predecessor.list != l {
return nil
}
return l.insertVal(a, predecessor)
}
// MoveToFront moves Node n to the front of List l.
// If n is not an element of l, the list is not modified.
// The Node must not be nil.
func (l *List[A]) MoveToFront(n *Node[A]) {
if n.list == l {
l.move(n, &l.root)
}
}
// MoveToBack moves Node n to the back of List l.
// If n is not an element of l, the list is not modified.
// The Node must not be nil.
func (l *List[A]) MoveToBack(n *Node[A]) {
if n.list == l {
l.move(n, l.root.prev)
}
}
// MoveBefore moves Node n to its new position before successor.
// If n or successor is not an element of l, or n == successor, the list is not
// modified. The Node and successor must not be nil.
func (l *List[A]) MoveBefore(n, successor *Node[A]) {
if n.list == l && successor.list == l {
l.move(n, successor.prev)
}
}
// MoveAfter moves Node n to its new position after predecessor.
// If n or predecessor is not an element of l, or n == predecessor, the list is
// not modified. The Node and predecessor must not be nil.
func (l *List[A]) MoveAfter(n, predecessor *Node[A]) {
if n.list == l && predecessor.list == l {
l.move(n, predecessor)
}
}
// PushBackList inserts a copy of List other at the back of List l.
// The Lists l and other may be the same. They must not be nil.
func (l *List[A]) PushBackList(other *List[A]) {
l.lazyInit()
n := other.Front()
sz := other.Len()
for i := 0; i < sz; i++ {
l.insertVal(n.Value, l.root.prev)
n = n.Next()
}
}
// PushFrontList inserts a copy of List other at the front of List l.
// The Lists l and other may be the same. They must not be nil.
func (l *List[A]) PushFrontList(other *List[A]) {
l.lazyInit()
n := other.Back()
sz := other.Len()
for i := 0; i < sz; i++ {
l.insertVal(n.Value, &l.root)
n = n.Prev()
}
}
+729
View File
@@ -0,0 +1,729 @@
package fn
import (
"math/rand"
"reflect"
"testing"
"testing/quick"
)
func GenList(r *rand.Rand) *List[uint32] {
size := int(r.Uint32() >> 24)
l := NewList[uint32]()
for i := 0; i < size; i++ {
l.PushBack(rand.Uint32())
}
return l
}
func GetRandNode(l *List[uint32], r *rand.Rand) *Node[uint32] {
if l.Len() == 0 {
return nil
}
idx := r.Uint32() % uint32(l.Len())
n := l.Front()
for i := 0; i < int(idx); i++ {
n = n.Next()
}
return n
}
func TestPushLenIncrement(t *testing.T) {
err := quick.Check(
func(l *List[uint32], x uint32, front bool) bool {
sz := l.Len()
if front {
l.PushFront(x)
} else {
l.PushBack(x)
}
sz2 := l.Len()
return sz2 == sz+1
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
vs[0] = reflect.ValueOf(GenList(r))
vs[1] = reflect.ValueOf(r.Uint32())
vs[2] = reflect.ValueOf(r.Uint32()%2 == 0)
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestRemoveLenDecrement(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32]) bool {
if l.Len() == 0 {
return true
}
sz := l.Len()
l.Remove(n)
sz2 := l.Len()
return sz2 == sz-1
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestPushGetCongruence(t *testing.T) {
err := quick.Check(
func(l *List[uint32], x uint32, front bool) bool {
if front {
l.PushFront(x)
return l.Front().Value == x
} else {
l.PushBack(x)
return l.Back().Value == x
}
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
vs[0] = reflect.ValueOf(GenList(r))
vs[1] = reflect.ValueOf(r.Uint32())
vs[2] = reflect.ValueOf(r.Uint32()%2 == 0)
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestInsertBeforeFrontIdentity(t *testing.T) {
err := quick.Check(
func(l *List[uint32], x uint32) bool {
if l == nil {
return true
}
nodeX := l.InsertBefore(x, l.Front())
return nodeX == l.Front()
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(r.Uint32())
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestInsertAfterBackIdentity(t *testing.T) {
err := quick.Check(
func(l *List[uint32], x uint32) bool {
if l == nil {
return true
}
nodeX := l.InsertAfter(x, l.Back())
return nodeX == l.Back()
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(r.Uint32())
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestInsertBeforeNextIdentity(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32], x uint32) bool {
if n == nil {
return true
}
nodeX := l.InsertBefore(x, n)
return nodeX.Next() == n
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
vs[2] = reflect.ValueOf(r.Uint32())
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestInsertAfterPrevIdentity(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32], x uint32) bool {
if n == nil {
return true
}
nodeX := l.InsertAfter(x, n)
return nodeX.Prev() == n
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
vs[2] = reflect.ValueOf(r.Uint32())
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestMoveToFrontFrontIdentity(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32]) bool {
if n == nil {
return true
}
l.MoveToFront(n)
return l.Front() == n
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestMoveToBackBackIdentity(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32]) bool {
if n == nil {
return true
}
l.MoveToBack(n)
return l.Back() == n
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestMoveBeforeFrontIsFront(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32]) bool {
if n == nil {
return true
}
l.MoveBefore(n, l.Front())
return l.Front() == n
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestMoveAfterBackIsBack(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32]) bool {
if n == nil {
return true
}
l.MoveAfter(n, l.Back())
return l.Back() == n
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestMultiMoveErasure(t *testing.T) {
err := quick.Check(
func(l *List[uint32], n *Node[uint32], m *Node[uint32]) bool {
if n == nil || m == nil || n == m {
return true
}
l.MoveToFront(n)
l.MoveToBack(n)
l.MoveBefore(n, m)
l.MoveAfter(n, m)
return m.Next() == n
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(GetRandNode(l, r))
vs[2] = reflect.ValueOf(GetRandNode(l, r))
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestPushListSymmetry(t *testing.T) {
copyList := func(l *List[uint32]) *List[uint32] {
c := NewList[uint32]()
for n := l.Front(); n != nil; n = n.Next() {
c.PushBack(n.Value)
}
return c
}
err := quick.Check(
func(l1 *List[uint32], l2 *List[uint32]) bool {
if l1.Len() == 0 || l2.Len() == 0 {
return true
}
l1Copy := copyList(l1)
l2Copy := copyList(l2)
l1.PushBackList(l2Copy)
l2.PushFrontList(l1Copy)
iter1 := l1.Front()
iter2 := l2.Front()
for i := 0; i < l1Copy.Len()+l2Copy.Len()-1; i++ {
if iter1.Value != iter2.Value {
return false
}
iter1 = iter1.Next()
iter2 = iter2.Next()
}
return true
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
l2 := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(l2)
},
},
)
if err != nil {
t.Fatal(err)
}
}
func TestIssue4103(t *testing.T) {
l1 := NewList[int]()
l1.PushBack(1)
l1.PushBack(2)
l2 := NewList[int]()
l2.PushBack(3)
l2.PushBack(4)
e := l1.Front()
l2.Remove(e) // l2 should not change because e is not an element of l2
if n := l2.Len(); n != 2 {
t.Errorf("l2.Len() = %d, want 2", n)
}
l1.InsertBefore(8, e)
if n := l1.Len(); n != 3 {
t.Errorf("l1.Len() = %d, want 3", n)
}
}
func TestIssue6349(t *testing.T) {
l := NewList[int]()
l.PushBack(1)
l.PushBack(2)
e := l.Front()
l.Remove(e)
if e.Value != 1 {
t.Errorf("e.value = %d, want 1", e.Value)
}
if e.Next() != nil {
t.Errorf("e.Next() != nil")
}
if e.Prev() != nil {
t.Errorf("e.Prev() != nil")
}
}
func checkListLen[V any](t *testing.T, l *List[V], length int) bool {
if n := l.Len(); n != length {
t.Errorf("l.Len() = %d, want %d", n, length)
return false
}
return true
}
func checkListPointers[V any](t *testing.T, l *List[V], es []*Node[V]) {
root := &l.root
if !checkListLen(t, l, len(es)) {
return
}
// zero length lists must be the zero value or properly initialized
// (sentinel circle)
if len(es) == 0 {
if l.root.next != nil && l.root.next != root ||
l.root.prev != nil && l.root.prev != root {
t.Errorf("l.root.next = %p, l.root.prev = %p;"+
"both should both be nil or %p", l.root.next,
l.root.prev, root)
}
return
}
// len(es) > 0
// check internal and external prev/next connections
for i, e := range es {
prev := root
Prev := (*Node[V])(nil)
if i > 0 {
prev = es[i-1]
Prev = prev
}
if p := e.prev; p != prev {
t.Errorf("elt[%d](%p).prev = %p, want %p", i, e, p,
prev)
}
if p := e.Prev(); p != Prev {
t.Errorf("elt[%d](%p).Prev() = %p, want %p", i, e, p,
Prev)
}
next := root
Next := (*Node[V])(nil)
if i < len(es)-1 {
next = es[i+1]
Next = next
}
if n := e.next; n != next {
t.Errorf("elt[%d](%p).next = %p, want %p", i, e, n,
next)
}
if n := e.Next(); n != Next {
t.Errorf("elt[%d](%p).Next() = %p, want %p", i, e, n,
Next)
}
}
}
func TestList(t *testing.T) {
l := NewList[int]()
checkListPointers(t, l, []*Node[int]{})
// Single element list
e := l.PushFront(5)
checkListPointers(t, l, []*Node[int]{e})
l.MoveToFront(e)
checkListPointers(t, l, []*Node[int]{e})
l.MoveToBack(e)
checkListPointers(t, l, []*Node[int]{e})
l.Remove(e)
checkListPointers(t, l, []*Node[int]{})
// Bigger list
e2 := l.PushFront(2)
e1 := l.PushFront(1)
e3 := l.PushBack(3)
e4 := l.PushBack(0)
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
l.Remove(e2)
checkListPointers(t, l, []*Node[int]{e1, e3, e4})
l.MoveToFront(e3) // move from middle
checkListPointers(t, l, []*Node[int]{e3, e1, e4})
l.MoveToFront(e1)
l.MoveToBack(e3) // move from middle
checkListPointers(t, l, []*Node[int]{e1, e4, e3})
l.MoveToFront(e3) // move from back
checkListPointers(t, l, []*Node[int]{e3, e1, e4})
l.MoveToFront(e3) // should be no-op
checkListPointers(t, l, []*Node[int]{e3, e1, e4})
l.MoveToBack(e3) // move from front
checkListPointers(t, l, []*Node[int]{e1, e4, e3})
l.MoveToBack(e3) // should be no-op
checkListPointers(t, l, []*Node[int]{e1, e4, e3})
e2 = l.InsertBefore(2, e1) // insert before front
checkListPointers(t, l, []*Node[int]{e2, e1, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e4) // insert before middle
checkListPointers(t, l, []*Node[int]{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e3) // insert before back
checkListPointers(t, l, []*Node[int]{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e1) // insert after front
checkListPointers(t, l, []*Node[int]{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e4) // insert after middle
checkListPointers(t, l, []*Node[int]{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e3) // insert after back
checkListPointers(t, l, []*Node[int]{e1, e4, e3, e2})
l.Remove(e2)
// Check standard iteration.
sum := 0
for e := l.Front(); e != nil; e = e.Next() {
sum += e.Value
}
if sum != 4 {
t.Errorf("sum over l = %d, want 4", sum)
}
// Clear all elements by iterating
var next *Node[int]
for e := l.Front(); e != nil; e = next {
next = e.Next()
l.Remove(e)
}
checkListPointers(t, l, []*Node[int]{})
}
func checkList[V comparable](t *testing.T, l *List[V], es []V) {
if !checkListLen(t, l, len(es)) {
return
}
i := 0
for e := l.Front(); e != nil; e = e.Next() {
le := e.Value
if le != es[i] {
t.Errorf("elt[%d].Value = %v, want %v", i, le, es[i])
}
i++
}
}
func TestExtending(t *testing.T) {
l1 := NewList[int]()
l2 := NewList[int]()
l1.PushBack(1)
l1.PushBack(2)
l1.PushBack(3)
l2.PushBack(4)
l2.PushBack(5)
l3 := NewList[int]()
l3.PushBackList(l1)
checkList(t, l3, []int{1, 2, 3})
l3.PushBackList(l2)
checkList(t, l3, []int{1, 2, 3, 4, 5})
l3 = NewList[int]()
l3.PushFrontList(l2)
checkList(t, l3, []int{4, 5})
l3.PushFrontList(l1)
checkList(t, l3, []int{1, 2, 3, 4, 5})
checkList(t, l1, []int{1, 2, 3})
checkList(t, l2, []int{4, 5})
l3 = NewList[int]()
l3.PushBackList(l1)
checkList(t, l3, []int{1, 2, 3})
l3.PushBackList(l3)
checkList(t, l3, []int{1, 2, 3, 1, 2, 3})
l3 = NewList[int]()
l3.PushFrontList(l1)
checkList(t, l3, []int{1, 2, 3})
l3.PushFrontList(l3)
checkList(t, l3, []int{1, 2, 3, 1, 2, 3})
l3 = NewList[int]()
l1.PushBackList(l3)
checkList(t, l1, []int{1, 2, 3})
l1.PushFrontList(l3)
checkList(t, l1, []int{1, 2, 3})
}
func TestRemove(t *testing.T) {
l := NewList[int]()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
checkListPointers(t, l, []*Node[int]{e1, e2})
e := l.Front()
l.Remove(e)
checkListPointers(t, l, []*Node[int]{e2})
l.Remove(e)
checkListPointers(t, l, []*Node[int]{e2})
}
func TestMove(t *testing.T) {
l := NewList[int]()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
e3 := l.PushBack(3)
e4 := l.PushBack(4)
l.MoveAfter(e3, e3)
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
l.MoveBefore(e2, e2)
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
l.MoveAfter(e3, e2)
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
l.MoveBefore(e2, e3)
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
l.MoveBefore(e2, e4)
checkListPointers(t, l, []*Node[int]{e1, e3, e2, e4})
e2, e3 = e3, e2
l.MoveBefore(e4, e1)
checkListPointers(t, l, []*Node[int]{e4, e1, e2, e3})
e1, e2, e3, e4 = e4, e1, e2, e3
l.MoveAfter(e4, e1)
checkListPointers(t, l, []*Node[int]{e1, e4, e2, e3})
e2, e3, e4 = e4, e2, e3
l.MoveAfter(e2, e3)
checkListPointers(t, l, []*Node[int]{e1, e3, e2, e4})
}
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
func TestZeroList(t *testing.T) {
var l1 = new(List[int])
l1.PushFront(1)
checkList(t, l1, []int{1})
var l2 = new(List[int])
l2.PushBack(1)
checkList(t, l2, []int{1})
var l3 = new(List[int])
l3.PushFrontList(l1)
checkList(t, l3, []int{1})
var l4 = new(List[int])
l4.PushBackList(l2)
checkList(t, l4, []int{1})
}
// Test that a list l is not modified when calling InsertBefore with a mark
// that is not an element of l.
func TestInsertBeforeUnknownMark(t *testing.T) {
var l List[int]
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertBefore(1, new(Node[int]))
checkList(t, &l, []int{1, 2, 3})
}
// Test that a list l is not modified when calling InsertAfter with a mark that
// is not an element of l.
func TestInsertAfterUnknownMark(t *testing.T) {
var l List[int]
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertAfter(1, new(Node[int]))
checkList(t, &l, []int{1, 2, 3})
}
// Test that a list l is not modified when calling MoveAfter or MoveBefore with
// a mark that is not an element of l.
func TestMoveUnknownMark(t *testing.T) {
var l1 List[int]
e1 := l1.PushBack(1)
var l2 List[int]
e2 := l2.PushBack(2)
l1.MoveAfter(e1, e2)
checkList(t, &l1, []int{1})
checkList(t, &l2, []int{2})
l1.MoveBefore(e1, e2)
checkList(t, &l1, []int{1})
checkList(t, &l2, []int{2})
}
+20
View File
@@ -213,3 +213,23 @@ func (o Option[A]) UnsafeFromSome() A {
}
panic("Option was None()")
}
// OptionToLeft can be used to convert an Option value into an Either, by
// providing the Right value that should be used if the Option value is None.
func OptionToLeft[O, L, R any](o Option[O], r R) Either[O, R] {
if o.IsSome() {
return NewLeft[O, R](o.some)
}
return NewRight[O, R](r)
}
// OptionToRight can be used to convert an Option value into an Either, by
// providing the Left value that should be used if the Option value is None.
func OptionToRight[O, L, R any](o Option[O], l L) Either[L, O] {
if o.IsSome() {
return NewRight[L, O](o.some)
}
return NewLeft[L, O](l)
}
+36 -24
View File
@@ -39,12 +39,17 @@ func Errf[T any](errString string, args ...any) Result[T] {
// Unpack extracts the value or error from the Result.
func (r Result[T]) Unpack() (T, error) {
var zero T
return r.left.UnwrapOr(zero), r.right.UnwrapOr(nil)
if r.IsErr() {
return zero, r.right
}
return r.left, nil
}
// Err exposes the underlying error of the result type as a normal error type.
func (r Result[T]) Err() error {
return r.right.some
return r.right
}
// IsOk returns true if the Result is a success value.
@@ -59,66 +64,73 @@ func (r Result[T]) IsErr() bool {
// Map applies a function to the success value if it exists.
func (r Result[T]) Map(f func(T) T) Result[T] {
if r.IsOk() {
return Ok(f(r.left.some))
return Result[T]{
MapLeft[T, error](f)(r.Either),
}
return r
}
// MapErr applies a function to the error value if it exists.
func (r Result[T]) MapErr(f func(error) error) Result[T] {
if r.IsErr() {
return Err[T](f(r.right.some))
return Result[T]{
MapRight[T](f)(r.Either),
}
return r
}
// Option returns the success value as an Option.
func (r Result[T]) Option() Option[T] {
return r.left
return r.Either.LeftToOption()
}
// WhenResult executes the given function if the Result is a success.
func (r Result[T]) WhenResult(f func(T)) {
r.left.WhenSome(func(t T) {
f(t)
})
r.WhenLeft(f)
}
// WhenErr executes the given function if the Result is an error.
func (r Result[T]) WhenErr(f func(error)) {
r.right.WhenSome(func(e error) {
f(e)
})
r.WhenRight(f)
}
// UnwrapOr returns the success value or a default value if it's an error.
func (r Result[T]) UnwrapOr(defaultValue T) T {
return r.left.UnwrapOr(defaultValue)
if r.IsErr() {
return defaultValue
}
return r.left
}
// UnwrapOrElse returns the success value or computes a value from a function
// if it's an error.
func (r Result[T]) UnwrapOrElse(f func() T) T {
return r.left.UnwrapOrFunc(f)
if r.IsErr() {
return f()
}
return r.left
}
// UnwrapOrFail returns the success value or fails the test if it's an error.
func (r Result[T]) UnwrapOrFail(t *testing.T) T {
t.Helper()
return r.left.UnwrapOrFail(t)
if r.IsErr() {
t.Fatalf("Result[%T] contained error: %v", r.left, r.right)
}
var zero T
return zero
}
// FlatMap applies a function that returns a Result to the success value if it
// exists.
func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] {
if r.IsOk() {
return f(r.left.some)
return r
}
return r
return f(r.left)
}
// AndThen is an alias for FlatMap. This along with OrElse can be used to
@@ -143,10 +155,10 @@ func (r Result[T]) OrElse(f func() Result[T]) Result[T] {
// it exists.
func FlatMap[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
if r.IsOk() {
return f(r.left.some)
return f(r.left)
}
return Err[B](r.right.some)
return Err[B](r.right)
}
// AndThen is an alias for FlatMap. This along with OrElse can be used to
+56 -3
View File
@@ -1,6 +1,13 @@
package fn
import "golang.org/x/exp/constraints"
import (
"context"
"runtime"
"sync"
"golang.org/x/exp/constraints"
"golang.org/x/sync/semaphore"
)
// Number is a type constraint for all numeric types in Go (integers,
// float and complex numbers)
@@ -46,7 +53,7 @@ func Map[A, B any](f func(A) B, s []A) []B {
// Filter creates a new slice of values where all the members of the returned
// slice pass the predicate that is supplied in the argument.
func Filter[A any](pred func(A) bool, s []A) []A {
func Filter[A any](pred Pred[A], s []A) []A {
res := make([]A, 0)
for _, val := range s {
@@ -85,7 +92,7 @@ func Foldr[A, B any](f func(A, B) B, seed B, s []A) B {
// Find returns the first value that passes the supplied predicate, or None if
// the value wasn't found.
func Find[A any](pred func(A) bool, s []A) Option[A] {
func Find[A any](pred Pred[A], s []A) Option[A] {
for _, val := range s {
if pred(val) {
return Some(val)
@@ -95,6 +102,23 @@ func Find[A any](pred func(A) bool, s []A) Option[A] {
return None[A]()
}
// FindIdx returns the first value that passes the supplied predicate along with
// its index in the slice. If no satisfactory value is found, None is returned.
func FindIdx[A any](pred Pred[A], s []A) Option[T2[int, A]] {
for i, val := range s {
if pred(val) {
return Some(NewT2[int, A](i, val))
}
}
return None[T2[int, A]]()
}
// Elem returns true if the element in the argument is found in the slice
func Elem[A comparable](a A, s []A) bool {
return Any(Eq(a), s)
}
// Flatten takes a slice of slices and returns a concatenation of those slices.
func Flatten[A any](s [][]A) []A {
sz := Foldr(
@@ -205,3 +229,32 @@ func Sum[B Number](items []B) B {
func HasDuplicates[A comparable](items []A) bool {
return len(NewSet(items...)) != len(items)
}
// ForEachConc maps the argument function over the slice, spawning a new
// goroutine for each element in the slice and then awaits all results before
// returning them.
func ForEachConc[A, B any](f func(A) B,
as []A) []B {
var wait sync.WaitGroup
ctx := context.Background()
sem := semaphore.NewWeighted(int64(runtime.NumCPU()))
bs := make([]B, len(as))
for i, a := range as {
i, a := i, a
sem.Acquire(ctx, 1)
wait.Add(1)
go func() {
bs[i] = f(a)
wait.Done()
sem.Release(1)
}()
}
wait.Wait()
return bs
}
+93
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"slices"
"testing"
"testing/quick"
"time"
"github.com/stretchr/testify/require"
)
@@ -281,3 +283,94 @@ func TestHasDuplicates(t *testing.T) {
})
}
}
// TestPropForEachConcMapIsomorphism ensures the property that ForEachConc and
// Map always yield the same results.
func TestPropForEachConcMapIsomorphism(t *testing.T) {
f := func(incSize int, s []int) bool {
inc := func(i int) int { return i + incSize }
mapped := Map(inc, s)
conc := ForEachConc(inc, s)
return slices.Equal(mapped, conc)
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
// TestPropForEachConcOutperformsMapWhenExpensive ensures the property that
// ForEachConc will beat Map in a race in circumstances where the computation in
// the argument closure is expensive.
func TestPropForEachConcOutperformsMapWhenExpensive(t *testing.T) {
f := func(incSize int, s []int) bool {
if len(s) < 2 {
// Intuitively we don't expect the extra overhead of
// ForEachConc to justify itself for list sizes of 1 or
// 0.
return true
}
inc := func(i int) int {
time.Sleep(time.Millisecond)
return i + incSize
}
c := make(chan bool, 1)
go func() {
Map(inc, s)
select {
case c <- false:
default:
}
}()
go func() {
ForEachConc(inc, s)
select {
case c <- true:
default:
}
}()
return <-c
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
func TestPropFindIdxFindIdentity(t *testing.T) {
f := func(div, mod uint8, s []uint8) bool {
if div == 0 || div == 1 {
return true
}
pred := func(i uint8) bool {
return i%div == mod
}
foundIdx := FindIdx(pred, s)
// onlyVal :: Option[T2[A, B]] -> Option[B]
onlyVal := MapOption(func(t2 T2[int, uint8]) uint8 {
return t2.Second()
})
valuesEqual := Find(pred, s) == onlyVal(foundIdx)
idxGetsVal := ElimOption(
foundIdx,
func() bool { return true },
func(t2 T2[int, uint8]) bool {
return s[t2.First()] == t2.Second()
})
return valuesEqual && idxGetsVal
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}
+57
View File
@@ -0,0 +1,57 @@
package fn
// T2 is the simplest 2-tuple type. It is useful for capturing ad hoc
// type conjunctions in a single value that can be easily dot-chained.
type T2[A, B any] struct {
first A
second B
}
// NewT2 is the canonical constructor for a T2. We include it because the fields
// themselves are unexported.
func NewT2[A, B any](a A, b B) T2[A, B] {
return T2[A, B]{
first: a,
second: b,
}
}
// First returns the first value in the T2.
func (t2 T2[A, B]) First() A {
return t2.first
}
// Second returns the second value in the T2.
func (t2 T2[A, B]) Second() B {
return t2.second
}
// Unpack ejects the 2-tuple's members into the multiple return values that
// are customary in go idiom.
func (t2 T2[A, B]) Unpack() (A, B) {
return t2.first, t2.second
}
// Pair takes two functions that share the same argument type and runs them
// both and produces a 2-tuple of the results.
func Pair[A, B, C any](f func(A) B, g func(A) C) func(A) T2[B, C] {
return func(a A) T2[B, C] {
return NewT2[B, C](f(a), g(a))
}
}
// MapFirst lifts the argument function into one that applies to the first
// element of a 2-tuple.
func MapFirst[A, B, C any](f func(A) B) func(T2[A, C]) T2[B, C] {
return func(t2 T2[A, C]) T2[B, C] {
return NewT2[B, C](f(t2.First()), t2.Second())
}
}
// MapSecond lifts the argument function into one that applies to the second
// element of a 2-tuple.
func MapSecond[A, B, C any](f func(A) B) func(T2[C, A]) T2[C, B] {
return func(t2 T2[C, A]) T2[C, B] {
return NewT2[C, B](t2.First(), f(t2.Second()))
}
}