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
8 changes: 3 additions & 5 deletions fn/conc_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package fn

import (
"sync"

"github.com/lightninglabs/neutrino/cache/lru"
)

// ConcurrentQueue is a typed concurrent-safe FIFO queue with unbounded
Expand All @@ -17,21 +15,21 @@ type ConcurrentQueue[T any] struct {

chanIn chan T
chanOut chan T
overflow *lru.List[T]
overflow *List[T]

wg sync.WaitGroup
quit chan 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{}),
}
}
Expand Down
115 changes: 102 additions & 13 deletions fn/either.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Comment thread
ProofOfKeags marked this conversation as resolved.
Outdated
if !e.isRight {
Comment thread
ProofOfKeags marked this conversation as resolved.
Outdated
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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there's no feedback when calling these WhenLeft or WhenRight? How do the callers know an effect has been applied to an Either obj or not?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This function should only be used if we want to conditionally run actions using the inner value on one side. If you need to somehow introspect this value to understand whether the closure was run or not, it's probably better to use ElimEither

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 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 None[O]()
return Either[L, O]{
isRight: false,
left: e.left,
}
}
}
139 changes: 139 additions & 0 deletions fn/either_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package fn
Comment thread
ProofOfKeags marked this conversation as resolved.
Outdated

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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: what does the comment mean smh ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Shake my head", means disappointment. I was making some commentary about how I couldn't just put len in the field as the type func(string) int even though it would be a sensible thing to be able to do. append has similar issues. Perhaps I should either remove the snarky comment or be more explicit about warning people not to try and refactor it away.

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 changes: 46 additions & 0 deletions fn/fn.go
Original file line number Diff line number Diff line change
@@ -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 changes: 0 additions & 17 deletions fn/func.go

This file was deleted.

2 changes: 1 addition & 1 deletion fn/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module github.com/lightningnetwork/lnd/fn
go 1.19

require (
github.com/lightninglabs/neutrino/cache v1.1.2
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b
golang.org/x/sync v0.7.0
)

require (
Expand Down
Loading