mirror of
https://github.com/IBM/fp-go.git
synced 2025-06-23 00:27:49 +02:00
initial checkin
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
126
README.md
126
README.md
@ -1,2 +1,124 @@
|
|||||||
# fp-go
|
# Functional programming library for golang
|
||||||
functional programming library for golang
|
|
||||||
|

|
||||||
|
|
||||||
|
## Design Goal
|
||||||
|
|
||||||
|
This library aims to provide a set of data types and functions that make it easy and fun to write maintainable and testable code in golang. It encourages the following patterns:
|
||||||
|
|
||||||
|
- write many small, testable and pure functions, i.e. functions that produce output only depending on their input and that do not execute side effects
|
||||||
|
- offer helpers to isolate side effects into lazily executed functions (IO)
|
||||||
|
- expose a consistent set of composition to create new functions from existing ones
|
||||||
|
- for each data type there exists a small set of composition functions
|
||||||
|
- these functions are called the same across all data types, so you only have to learn a small number of function names
|
||||||
|
- the semantic of functions of the same name is consistent across all data types
|
||||||
|
|
||||||
|
### How does this play with the [🧘🏽 Zen Of Go](https://the-zen-of-go.netlify.app/)?
|
||||||
|
|
||||||
|
#### 🧘🏽 Each package fulfils a single purpose
|
||||||
|
|
||||||
|
✔️ Each of the top level packages (e.g. Option, Either, Task, ...) fulfils the purpose of defining the respective data type and implementing the set of common operations for this data type.
|
||||||
|
|
||||||
|
#### 🧘🏽 Handle errors explicitly
|
||||||
|
|
||||||
|
✔️ The library makes a clear distinction between that operations that cannot fail by design and operations that can fail. Failure is represented via the `Either` type and errors are handled explicitly by using `Either`'s monadic set of operations.
|
||||||
|
|
||||||
|
#### 🧘🏽 Return early rather than nesting deeply
|
||||||
|
|
||||||
|
✔️ We recommend to implement simple, small functions that implement one feature and that would typically not invoke other functions. Interaction with other functions is done by function composition and the composition makes sure to run one function after the other. In the error case the `Either` monad makes sure to skip the error path.
|
||||||
|
|
||||||
|
#### 🧘🏽 Leave concurrency to the caller
|
||||||
|
|
||||||
|
✔️ All operations are synchronous by default, including `Task`. Concurrency must be coded by the consumer of these functions explicitly, but the implementation is ready to deal with concurrent usage.
|
||||||
|
|
||||||
|
#### 🧘🏽 Before you launch a goroutine, know when it will stop
|
||||||
|
|
||||||
|
🤷🏽 This is left to the user of the library since the library itself will not start goroutines on its own. The Task monad offers support for cancellation via the golang context, though.
|
||||||
|
|
||||||
|
#### 🧘🏽 Avoid package level state
|
||||||
|
|
||||||
|
✔️ No package level state anywhere, this would be a significant anti-pattern
|
||||||
|
|
||||||
|
#### 🧘🏽 Simplicity matters
|
||||||
|
|
||||||
|
✔️ The library is simple in the sense that it offers a small, consistent interface to a variety of data types. Users can concentrate on implementing business logic rather than dealing with low level data structures.
|
||||||
|
|
||||||
|
#### 🧘🏽 Write tests to lock in the behaviour of your package’s API
|
||||||
|
|
||||||
|
🟡 The programming pattern suggested by this library encourages writing test cases. The library itself also has a growing number of tests, but not enough, yet. TBD
|
||||||
|
|
||||||
|
#### 🧘🏽 If you think it’s slow, first prove it with a benchmark
|
||||||
|
|
||||||
|
✔️ Absolutely. If you think the function composition offered by this library is too slow, please provide a benchmark.
|
||||||
|
|
||||||
|
#### 🧘🏽 Moderation is a virtue
|
||||||
|
|
||||||
|
✔️ The library does not implement its own goroutines and also does not require any expensive synchronization primitives. Coordination of Tasks is implemented via atomic counters without additional primitives. Channels are only used in the `Wait` function of a Task that should be invoked at most once in a complete application.
|
||||||
|
|
||||||
|
#### 🧘🏽 Maintainability counts
|
||||||
|
|
||||||
|
✔️ Code that consumes this library is easy to maintain because of the small and concise set of operations exposed. Also the suggested programming paradigm to decompose an application into small functions increases maintainability, because these functions are easy to understand and if they are pure, it's often sufficient to look at the type signature to understand the purpose.
|
||||||
|
|
||||||
|
The library itself also comprises many small functions, but it's admittedly harder to maintain than code that uses it. However this asymmetry is intended because it offloads complexity from users into a central component.
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### Generics
|
||||||
|
|
||||||
|
All monadic operations are implemented via generics, i.e. they offer a type safe way to compose operations. This allows for convenient IDE support and also gives confidence about the correctness of the composition at compile time.
|
||||||
|
|
||||||
|
Downside is that this will result in different versions of each operation per type, these versions are generated by the golang compiler at build time (unlike type erasure in languages such as Java of TypeScript). This might lead to large binaries for codebases with many different types. If this is a concern, you can always implement type erasure on top, i.e. use the monadic operations with the `any` type as if generics were not supported. You loose type safety, but this might result in smaller binaries.
|
||||||
|
|
||||||
|
### Use of the [~ Operator](https://go.googlesource.com/proposal/+/master/design/47781-parameterized-go-ast.md)
|
||||||
|
|
||||||
|
The FP library attempts to be easy to consume and one aspect of this is the definition of higher level type definitions instead of having to use their low level equivalent. It is e.g. more convenient and readable to use
|
||||||
|
|
||||||
|
```go
|
||||||
|
TaskEither[E, A]
|
||||||
|
```
|
||||||
|
|
||||||
|
than
|
||||||
|
|
||||||
|
```go
|
||||||
|
func(func(Either.Either[E, A]))
|
||||||
|
```
|
||||||
|
|
||||||
|
although both are logically equivalent. At the time of this writing the go type system does not support generic type aliases, only generic type definition, i.e. it is not possible to write:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TaskEither[E, A any] = T.Task[ET.Either[E, A]]
|
||||||
|
```
|
||||||
|
|
||||||
|
only
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TaskEither[E, A any] T.Task[ET.Either[E, A]]
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes a big difference, because in the second case the type `TaskEither[E, A any]` is considered a completely new type, not compatible to its right hand side, so it's not just a shortcut but a fully new type.
|
||||||
|
|
||||||
|
From the implementation perspective however there is no reason to restrict the implementation to the new type, it can be generic for all compatible types. The way to express this in go is the [~](https://go.googlesource.com/proposal/+/master/design/47781-parameterized-go-ast.md) operator. This comes with some quite complicated type declarations in some cases, which undermines the goal of the library to be easy to use.
|
||||||
|
|
||||||
|
For that reason there exist sub-packages called `Generic` for all higher level types. These packages contain the fully generic implementation of the operations, preferring abstraction over usability. These packages are not meant to be used by end-users but are meant to be used by library extensions. The implementation for the convenient higher level types specializes the generic implementation for the particular higher level type, i.e. this layer does not contain any business logic but only *type magic*.
|
||||||
|
|
||||||
|
### Higher Kinded Types
|
||||||
|
|
||||||
|
Go does not support higher kinded types (HKT). Such types occur if a generic type itself is parametrized by another generic type. Example:
|
||||||
|
|
||||||
|
The `Map` operation for `Task` is defined as:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Map[A, B any](f func(A) B) func(Task[A]) Task[B]
|
||||||
|
```
|
||||||
|
|
||||||
|
and in fact the equivalent operations for all other mondas follow the same pattern, we could try to introduce a new type for `Task` (without a parameter) as a HKT, e.g. like so (made-up syntax, does not work in go):
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Map[HKT, A, B any](f func(A) B) func(HKT[A]) HKT[B]
|
||||||
|
```
|
||||||
|
|
||||||
|
this would be the completely generic method signature for all possible monads. In particular in many cases it is possible to compose functions independent of the concrete knowledge of the actual `HKT`. From the perspective of a library this is the ideal situation because then a particular algorithm only has to be implemented and tested once.
|
||||||
|
|
||||||
|
This FP library addresses this by introducing the HKTs as individual types, e.g. `HKT[A]` would be represented as a new generic type `HKTA`. This loses the correlation to the type `A` but allows to implement generic algorithms, at the price of readability.
|
||||||
|
|
||||||
|
For that reason these implementations are kept in the `internal` package. These are meant to be used by the library itself or by extensions, not by end users.
|
||||||
|
72
apply/sequence.go
Normal file
72
apply/sequence.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package Apply
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
T "github.com/ibm/fp-go/tuple"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tupleConstructor1[A any]() func(A) T.Tuple1[A] {
|
||||||
|
return F.Curry1(T.MakeTuple1[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func tupleConstructor2[A, B any]() func(A) func(B) T.Tuple2[A, B] {
|
||||||
|
return F.Curry2(T.MakeTuple2[A, B])
|
||||||
|
}
|
||||||
|
|
||||||
|
func tupleConstructor3[A, B, C any]() func(A) func(B) func(C) T.Tuple3[A, B, C] {
|
||||||
|
return F.Curry3(T.MakeTuple3[A, B, C])
|
||||||
|
}
|
||||||
|
|
||||||
|
func tupleConstructor4[A, B, C, D any]() func(A) func(B) func(C) func(D) T.Tuple4[A, B, C, D] {
|
||||||
|
return F.Curry4(T.MakeTuple4[A, B, C, D])
|
||||||
|
}
|
||||||
|
|
||||||
|
func SequenceT1[A, HKTA, HKT1A any](
|
||||||
|
fmap func(HKTA, func(A) T.Tuple1[A]) HKT1A,
|
||||||
|
a HKTA) HKT1A {
|
||||||
|
return fmap(a, tupleConstructor1[A]())
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKTA = HKT[A]
|
||||||
|
// HKTB = HKT[B]
|
||||||
|
// HKT2AB = HKT[Tuple[A, B]]
|
||||||
|
// HKTFB2AB = HKT[func(B)Tuple[A, B]]
|
||||||
|
func SequenceT2[A, B, HKTA, HKTB, HKTFB2AB, HKT2AB any](
|
||||||
|
fmap func(HKTA, func(A) func(B) T.Tuple2[A, B]) HKTFB2AB,
|
||||||
|
fap1 func(HKTFB2AB, HKTB) HKT2AB,
|
||||||
|
a HKTA, b HKTB,
|
||||||
|
) HKT2AB {
|
||||||
|
return fap1(fmap(a, tupleConstructor2[A, B]()), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKTA = HKT[A]
|
||||||
|
// HKTB = HKT[B]
|
||||||
|
// HKTC = HKT[C]
|
||||||
|
// HKT3ABC = HKT[Tuple[A, B, C]]
|
||||||
|
// HKTFB3ABC = HKT[func(B)func(C)Tuple[A, B, C]]
|
||||||
|
// HKTFC3ABC = HKT[func(C)Tuple[A, B, C]]
|
||||||
|
func SequenceT3[A, B, C, HKTA, HKTB, HKTC, HKTFB3ABC, HKTFC3ABC, HKT3ABC any](
|
||||||
|
fmap func(HKTA, func(A) func(B) func(C) T.Tuple3[A, B, C]) HKTFB3ABC,
|
||||||
|
fap1 func(HKTFB3ABC, HKTB) HKTFC3ABC,
|
||||||
|
fap2 func(HKTFC3ABC, HKTC) HKT3ABC,
|
||||||
|
|
||||||
|
a HKTA, b HKTB, c HKTC) HKT3ABC {
|
||||||
|
return fap2(fap1(fmap(a, tupleConstructor3[A, B, C]()), b), c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKTA = HKT[A]
|
||||||
|
// HKTB = HKT[B]
|
||||||
|
// HKTC = HKT[C]
|
||||||
|
// HKT3ABCD = HKT[Tuple[A, B, C, D]]
|
||||||
|
// HKTFB3ABCD = HKT[func(B)func(C)func(D)Tuple[A, B, C, D]]
|
||||||
|
// HKTFC3ABCD = HKT[func(C)func(D)Tuple[A, B, C, D]]
|
||||||
|
// HKTFD3ABCD = HKT[func(D)Tuple[A, B, C, D]]
|
||||||
|
func SequenceT4[A, B, C, D, HKTA, HKTB, HKTC, HKTD, HKTFB4ABCD, HKTFC4ABCD, HKTFD4ABCD, HKT4ABCD any](
|
||||||
|
fmap func(HKTA, func(A) func(B) func(C) func(D) T.Tuple4[A, B, C, D]) HKTFB4ABCD,
|
||||||
|
fap1 func(HKTFB4ABCD, HKTB) HKTFC4ABCD,
|
||||||
|
fap2 func(HKTFC4ABCD, HKTC) HKTFD4ABCD,
|
||||||
|
fap3 func(HKTFD4ABCD, HKTD) HKT4ABCD,
|
||||||
|
|
||||||
|
a HKTA, b HKTB, c HKTC, d HKTD) HKT4ABCD {
|
||||||
|
return fap3(fap2(fap1(fmap(a, tupleConstructor4[A, B, C, D]()), b), c), d)
|
||||||
|
}
|
279
array/array.go
Normal file
279
array/array.go
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
G "github.com/ibm/fp-go/array/generic"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/ibm/fp-go/internal/array"
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
"github.com/ibm/fp-go/tuple"
|
||||||
|
)
|
||||||
|
|
||||||
|
// From constructs an array from a set of variadic arguments
|
||||||
|
func From[A any](data ...A) []A {
|
||||||
|
return G.From[[]A](data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeBy returns a `Array` of length `n` with element `i` initialized with `f(i)`.
|
||||||
|
func MakeBy[A any](n int, f func(int) A) []A {
|
||||||
|
// sanity check
|
||||||
|
if n <= 0 {
|
||||||
|
return Empty[A]()
|
||||||
|
}
|
||||||
|
// run the generator function across the input
|
||||||
|
as := make([]A, n)
|
||||||
|
for i := n - 1; i >= 0; i-- {
|
||||||
|
as[i] = f(i)
|
||||||
|
}
|
||||||
|
return as
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replicate creates a `Array` containing a value repeated the specified number of times.
|
||||||
|
func Replicate[A any](n int, a A) []A {
|
||||||
|
return MakeBy(n, F.Constant1[int](a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMap[A, B any](as []A, f func(a A) B) []B {
|
||||||
|
return G.MonadMap[[]A, []B](as, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMapRef[A, B any](as []A, f func(a *A) B) []B {
|
||||||
|
count := len(as)
|
||||||
|
bs := make([]B, count)
|
||||||
|
for i := count - 1; i >= 0; i-- {
|
||||||
|
bs[i] = f(&as[i])
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map[A, B any](f func(a A) B) func([]A) []B {
|
||||||
|
return F.Bind2nd(MonadMap[A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapRef[A, B any](f func(a *A) B) func([]A) []B {
|
||||||
|
return F.Bind2nd(MonadMapRef[A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filter[A any](fa []A, pred func(A) bool) []A {
|
||||||
|
var result []A
|
||||||
|
count := len(fa)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
a := fa[i]
|
||||||
|
if pred(a) {
|
||||||
|
result = append(result, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRef[A any](fa []A, pred func(a *A) bool) []A {
|
||||||
|
var result []A
|
||||||
|
count := len(fa)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
a := fa[i]
|
||||||
|
if pred(&a) {
|
||||||
|
result = append(result, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterMapRef[A, B any](fa []A, pred func(a *A) bool, f func(a *A) B) []B {
|
||||||
|
var result []B
|
||||||
|
count := len(fa)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
a := fa[i]
|
||||||
|
if pred(&a) {
|
||||||
|
result = append(result, f(&a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func Filter[A any](pred func(A) bool) func([]A) []A {
|
||||||
|
return F.Bind2nd(filter[A], pred)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterRef[A any](pred func(*A) bool) func([]A) []A {
|
||||||
|
return F.Bind2nd(filterRef[A], pred)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadFilterMap[A, B any](fa []A, f func(a A) O.Option[B]) []B {
|
||||||
|
return G.MonadFilterMap[[]A, []B](fa, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterMap[A, B any](f func(a A) O.Option[B]) func([]A) []B {
|
||||||
|
return G.FilterMap[[]A, []B](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterMapRef[A, B any](pred func(a *A) bool, f func(a *A) B) func([]A) []B {
|
||||||
|
return func(fa []A) []B {
|
||||||
|
return filterMapRef(fa, pred, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reduceRef[A, B any](fa []A, f func(B, *A) B, initial B) B {
|
||||||
|
current := initial
|
||||||
|
count := len(fa)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
current = f(current, &fa[i])
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func Reduce[A, B any](f func(B, A) B, initial B) func([]A) B {
|
||||||
|
return func(as []A) B {
|
||||||
|
return array.Reduce(as, f, initial)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReduceRef[A, B any](f func(B, *A) B, initial B) func([]A) B {
|
||||||
|
return func(as []A) B {
|
||||||
|
return reduceRef(as, f, initial)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Append[A any](as []A, a A) []A {
|
||||||
|
return G.Append(as, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsEmpty[A any](as []A) bool {
|
||||||
|
return array.IsEmpty(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNonEmpty[A any](as []A) bool {
|
||||||
|
return len(as) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func Empty[A any]() []A {
|
||||||
|
return G.Empty[[]A]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Zero[A any]() []A {
|
||||||
|
return Empty[A]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of constructs a single element array
|
||||||
|
func Of[A any](a A) []A {
|
||||||
|
return G.Of[[]A](a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChain[A, B any](fa []A, f func(a A) []B) []B {
|
||||||
|
return array.Reduce(fa, func(bs []B, a A) []B {
|
||||||
|
return append(bs, f(a)...)
|
||||||
|
}, Zero[B]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Chain[A, B any](f func(a A) []B) func([]A) []B {
|
||||||
|
return F.Bind2nd(MonadChain[A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadAp[A, B any](fab []func(A) B, fa []A) []B {
|
||||||
|
return MonadChain(fab, F.Bind1st(MonadMap[A, B], fa))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ap[A, B any](fa []A) func([]func(A) B) []B {
|
||||||
|
return F.Bind2nd(MonadAp[A, B], fa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Match[A, B any](onEmpty func() B, onNonEmpty func([]A) B) func([]A) B {
|
||||||
|
return func(as []A) B {
|
||||||
|
if IsEmpty(as) {
|
||||||
|
return onEmpty()
|
||||||
|
}
|
||||||
|
return onNonEmpty(as)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tail[A any](as []A) O.Option[[]A] {
|
||||||
|
return G.Tail(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Head[A any](as []A) O.Option[A] {
|
||||||
|
return G.Head(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func First[A any](as []A) O.Option[A] {
|
||||||
|
return G.First(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Last[A any](as []A) O.Option[A] {
|
||||||
|
return G.Last(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrependAll[A any](middle A) func([]A) []A {
|
||||||
|
return func(as []A) []A {
|
||||||
|
count := len(as)
|
||||||
|
dst := count * 2
|
||||||
|
result := make([]A, dst)
|
||||||
|
for i := count - 1; i >= 0; i-- {
|
||||||
|
dst--
|
||||||
|
result[dst] = as[i]
|
||||||
|
dst--
|
||||||
|
result[dst] = middle
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Intersperse[A any](middle A) func([]A) []A {
|
||||||
|
prepend := PrependAll(middle)
|
||||||
|
return func(as []A) []A {
|
||||||
|
if IsEmpty(as) {
|
||||||
|
return as
|
||||||
|
}
|
||||||
|
return prepend(as)[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A {
|
||||||
|
concatAll := ConcatAll[A](m)(m.Empty())
|
||||||
|
return func(middle A) func([]A) A {
|
||||||
|
return Match(m.Empty, F.Flow2(Intersperse(middle), concatAll))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flatten[A any](mma [][]A) []A {
|
||||||
|
return MonadChain(mma, F.Identity[[]A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Slice[A any](low, high int) func(as []A) []A {
|
||||||
|
return array.Slice[[]A](low, high)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lookup[A any](idx int) func([]A) O.Option[A] {
|
||||||
|
return G.Lookup[[]A](idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpsertAt[A any](a A) func([]A) []A {
|
||||||
|
return G.UpsertAt[[]A](a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Size[A any](as []A) int {
|
||||||
|
return G.Size(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadPartition[A any](as []A, pred func(A) bool) tuple.Tuple2[[]A, []A] {
|
||||||
|
return G.MonadPartition(as, pred)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partition creates two new arrays out of one, the left result contains the elements
|
||||||
|
// for which the predicate returns false, the right one those for which the predicate returns true
|
||||||
|
func Partition[A any](pred func(A) bool) func([]A) tuple.Tuple2[[]A, []A] {
|
||||||
|
return G.Partition[[]A](pred)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil checks if the array is set to nil
|
||||||
|
func IsNil[A any](as []A) bool {
|
||||||
|
return array.IsNil(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonNil checks if the array is set to nil
|
||||||
|
func IsNonNil[A any](as []A) bool {
|
||||||
|
return array.IsNonNil(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstNil returns a nil array
|
||||||
|
func ConstNil[A any]() []A {
|
||||||
|
return array.ConstNil[[]A]()
|
||||||
|
}
|
129
array/array_test.go
Normal file
129
array/array_test.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/ibm/fp-go/internal/utils"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
S "github.com/ibm/fp-go/string"
|
||||||
|
T "github.com/ibm/fp-go/tuple"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMap1(t *testing.T) {
|
||||||
|
|
||||||
|
src := []string{"a", "b", "c"}
|
||||||
|
|
||||||
|
up := Map(strings.ToUpper)(src)
|
||||||
|
|
||||||
|
var up1 = []string{}
|
||||||
|
for _, s := range src {
|
||||||
|
up1 = append(up1, strings.ToUpper(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
var up2 = []string{}
|
||||||
|
for i := range src {
|
||||||
|
up2 = append(up2, strings.ToUpper(src[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, up, up1)
|
||||||
|
assert.Equal(t, up, up2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMap(t *testing.T) {
|
||||||
|
|
||||||
|
mapper := Map(utils.Upper)
|
||||||
|
|
||||||
|
src := []string{"a", "b", "c"}
|
||||||
|
|
||||||
|
dst := mapper(src)
|
||||||
|
|
||||||
|
assert.Equal(t, dst, []string{"A", "B", "C"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduce(t *testing.T) {
|
||||||
|
|
||||||
|
values := MakeBy(101, F.Identity[int])
|
||||||
|
|
||||||
|
sum := func(val int, current int) int {
|
||||||
|
return val + current
|
||||||
|
}
|
||||||
|
reducer := Reduce(sum, 0)
|
||||||
|
|
||||||
|
result := reducer(values)
|
||||||
|
assert.Equal(t, result, 5050)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
assert.True(t, IsNonEmpty(MakeBy(101, F.Identity[int])))
|
||||||
|
assert.True(t, IsEmpty([]int{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAp(t *testing.T) {
|
||||||
|
assert.Equal(t,
|
||||||
|
[]int{2, 4, 6, 3, 6, 9},
|
||||||
|
F.Pipe1(
|
||||||
|
[]func(int) int{
|
||||||
|
utils.Double,
|
||||||
|
utils.Triple,
|
||||||
|
},
|
||||||
|
Ap[int, int]([]int{1, 2, 3}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntercalate(t *testing.T) {
|
||||||
|
is := Intercalate(S.Monoid)("-")
|
||||||
|
|
||||||
|
assert.Equal(t, "", is(Empty[string]()))
|
||||||
|
assert.Equal(t, "a", is([]string{"a"}))
|
||||||
|
assert.Equal(t, "a-b-c", is([]string{"a", "b", "c"}))
|
||||||
|
assert.Equal(t, "a--c", is([]string{"a", "", "c"}))
|
||||||
|
assert.Equal(t, "a-b", is([]string{"a", "b"}))
|
||||||
|
assert.Equal(t, "a-b-c-d", is([]string{"a", "b", "c", "d"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrependAll(t *testing.T) {
|
||||||
|
empty := Empty[int]()
|
||||||
|
prep := PrependAll(0)
|
||||||
|
assert.Equal(t, empty, prep(empty))
|
||||||
|
assert.Equal(t, []int{0, 1, 0, 2, 0, 3}, prep([]int{1, 2, 3}))
|
||||||
|
assert.Equal(t, []int{0, 1}, prep([]int{1}))
|
||||||
|
assert.Equal(t, []int{0, 1, 0, 2, 0, 3, 0, 4}, prep([]int{1, 2, 3, 4}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlatten(t *testing.T) {
|
||||||
|
assert.Equal(t, []int{1, 2, 3}, Flatten([][]int{{1}, {2}, {3}}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookup(t *testing.T) {
|
||||||
|
data := []int{0, 1, 2}
|
||||||
|
none := O.None[int]()
|
||||||
|
|
||||||
|
assert.Equal(t, none, Lookup[int](-1)(data))
|
||||||
|
assert.Equal(t, none, Lookup[int](10)(data))
|
||||||
|
assert.Equal(t, O.Some(1), Lookup[int](1)(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice(t *testing.T) {
|
||||||
|
data := []int{0, 1, 2, 3}
|
||||||
|
|
||||||
|
assert.Equal(t, []int{1, 2}, Slice[int](1, 3)(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrom(t *testing.T) {
|
||||||
|
assert.Equal(t, []int{1, 2, 3}, From(1, 2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartition(t *testing.T) {
|
||||||
|
|
||||||
|
pred := func(n int) bool {
|
||||||
|
return n > 2
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, T.MakeTuple2(Empty[int](), Empty[int]()), Partition(pred)(Empty[int]()))
|
||||||
|
assert.Equal(t, T.MakeTuple2(From(1), From(3)), Partition(pred)(From(1, 3)))
|
||||||
|
}
|
25
array/eq.go
Normal file
25
array/eq.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
E "github.com/ibm/fp-go/eq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func equals[T any](left []T, right []T, eq func(T, T) bool) bool {
|
||||||
|
if len(left) != len(right) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v1 := range left {
|
||||||
|
v2 := right[i]
|
||||||
|
if !eq(v1, v2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eq[T any](e E.Eq[T]) E.Eq[[]T] {
|
||||||
|
eq := e.Equals
|
||||||
|
return E.FromEquals(func(left, right []T) bool {
|
||||||
|
return equals(left, right, eq)
|
||||||
|
})
|
||||||
|
}
|
109
array/generic/array.go
Normal file
109
array/generic/array.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package generic
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/ibm/fp-go/internal/array"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
"github.com/ibm/fp-go/tuple"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Of constructs a single element array
|
||||||
|
func Of[GA ~[]A, A any](value A) GA {
|
||||||
|
return GA{value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From constructs an array from a set of variadic arguments
|
||||||
|
func From[GA ~[]A, A any](data ...A) GA {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lookup[GA ~[]A, A any](idx int) func(GA) O.Option[A] {
|
||||||
|
none := O.None[A]()
|
||||||
|
if idx < 0 {
|
||||||
|
return F.Constant1[GA](none)
|
||||||
|
}
|
||||||
|
return func(as GA) O.Option[A] {
|
||||||
|
if idx < len(as) {
|
||||||
|
return O.Some(as[idx])
|
||||||
|
}
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tail[GA ~[]A, A any](as GA) O.Option[GA] {
|
||||||
|
if array.IsEmpty(as) {
|
||||||
|
return O.None[GA]()
|
||||||
|
}
|
||||||
|
return O.Some(as[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Head[GA ~[]A, A any](as GA) O.Option[A] {
|
||||||
|
if array.IsEmpty(as) {
|
||||||
|
return O.None[A]()
|
||||||
|
}
|
||||||
|
return O.Some(as[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func First[GA ~[]A, A any](as GA) O.Option[A] {
|
||||||
|
return Head(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Last[GA ~[]A, A any](as GA) O.Option[A] {
|
||||||
|
if array.IsEmpty(as) {
|
||||||
|
return O.None[A]()
|
||||||
|
}
|
||||||
|
return O.Some(as[len(as)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Append[GA ~[]A, A any](as GA, a A) GA {
|
||||||
|
return array.Append(as, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Empty[GA ~[]A, A any]() GA {
|
||||||
|
return array.Empty[GA]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpsertAt[GA ~[]A, A any](a A) func(GA) GA {
|
||||||
|
return array.UpsertAt[GA](a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB {
|
||||||
|
return array.MonadMap[GA, GB](as, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Size[GA ~[]A, A any](as GA) int {
|
||||||
|
return len(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(a A) O.Option[B]) GB {
|
||||||
|
return array.Reduce(fa, func(bs GB, a A) GB {
|
||||||
|
return O.MonadFold(f(a), F.Constant(bs), F.Bind1st(Append[GB, B], bs))
|
||||||
|
}, Empty[GB]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadFilterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(a A) O.Option[B]) GB {
|
||||||
|
return filterMap[GA, GB](fa, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterMap[GA ~[]A, GB ~[]B, A, B any](f func(a A) O.Option[B]) func(GA) GB {
|
||||||
|
return F.Bind2nd(MonadFilterMap[GA, GB, A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadPartition[GA ~[]A, A any](as GA, pred func(A) bool) tuple.Tuple2[GA, GA] {
|
||||||
|
left := Empty[GA]()
|
||||||
|
right := Empty[GA]()
|
||||||
|
array.Reduce(as, func(c bool, a A) bool {
|
||||||
|
if pred(a) {
|
||||||
|
right = append(right, a)
|
||||||
|
} else {
|
||||||
|
left = append(left, a)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}, true)
|
||||||
|
// returns the partition
|
||||||
|
return tuple.MakeTuple2(left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Partition[GA ~[]A, A any](pred func(A) bool) func(GA) tuple.Tuple2[GA, GA] {
|
||||||
|
return F.Bind2nd(MonadPartition[GA, A], pred)
|
||||||
|
}
|
26
array/generic/sort.go
Normal file
26
array/generic/sort.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package generic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
O "github.com/ibm/fp-go/ord"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sort implements a stable sort on the array given the provided ordering
|
||||||
|
func Sort[GA ~[]T, T any](ord O.Ord[T]) func(ma GA) GA {
|
||||||
|
|
||||||
|
return func(ma GA) GA {
|
||||||
|
// nothing to sort
|
||||||
|
l := len(ma)
|
||||||
|
if l < 2 {
|
||||||
|
return ma
|
||||||
|
}
|
||||||
|
// copy
|
||||||
|
cpy := make(GA, l)
|
||||||
|
copy(cpy, ma)
|
||||||
|
sort.Slice(cpy, func(i, j int) bool {
|
||||||
|
return ord.Compare(cpy[i], cpy[j]) < 0
|
||||||
|
})
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
}
|
10
array/magma.go
Normal file
10
array/magma.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
M "github.com/ibm/fp-go/magma"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConcatAll[A any](m M.Magma[A]) func(A) func([]A) A {
|
||||||
|
return F.Bind1st(Reduce[A, A], m.Concat)
|
||||||
|
}
|
21
array/magma_test.go
Normal file
21
array/magma_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
M "github.com/ibm/fp-go/magma"
|
||||||
|
)
|
||||||
|
|
||||||
|
var subInt = M.MakeMagma(func(first int, second int) int {
|
||||||
|
return first - second
|
||||||
|
})
|
||||||
|
|
||||||
|
func TestConcatAll(t *testing.T) {
|
||||||
|
|
||||||
|
var subAll = ConcatAll(subInt)(0)
|
||||||
|
|
||||||
|
assert.Equal(t, subAll([]int{1, 2, 3}), -6)
|
||||||
|
|
||||||
|
}
|
43
array/monoid.go
Normal file
43
array/monoid.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ibm/fp-go/internal/array"
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func concat[T any](left, right []T) []T {
|
||||||
|
// some performance checks
|
||||||
|
ll := len(left)
|
||||||
|
lr := len(right)
|
||||||
|
if ll == 0 {
|
||||||
|
return right
|
||||||
|
}
|
||||||
|
if lr == 0 {
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
// need to copy
|
||||||
|
buf := make([]T, ll+lr)
|
||||||
|
copy(buf[copy(buf, left):], right)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func Monoid[T any]() M.Monoid[[]T] {
|
||||||
|
return M.MakeMonoid(concat[T], Empty[T]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLen[A any](count int, data []A) int {
|
||||||
|
return count + len(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConcatAll efficiently concatenates the input arrays into a final array
|
||||||
|
func ArrayConcatAll[A any](data ...[]A) []A {
|
||||||
|
// get the full size
|
||||||
|
count := array.Reduce(data, addLen[A], 0)
|
||||||
|
buf := make([]A, count)
|
||||||
|
// copy
|
||||||
|
array.Reduce(data, func(idx int, seg []A) int {
|
||||||
|
return idx + copy(buf[idx:], seg)
|
||||||
|
}, 0)
|
||||||
|
// returns the final array
|
||||||
|
return buf
|
||||||
|
}
|
11
array/monoid_test.go
Normal file
11
array/monoid_test.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
M "github.com/ibm/fp-go/monoid/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMonoid(t *testing.T) {
|
||||||
|
M.AssertLaws(t, Monoid[int]())([][]int{{}, {1}, {1, 2}})
|
||||||
|
}
|
43
array/sequence.go
Normal file
43
array/sequence.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||||
|
|
||||||
|
// HKTA = HKT<A>
|
||||||
|
// HKTRA = HKT<[]A>
|
||||||
|
// HKTFRA = HKT<func(A)[]A>
|
||||||
|
|
||||||
|
// Sequence takes an `Array` where elements are `HKT<A>` (higher kinded type) and,
|
||||||
|
// using an applicative of that `HKT`, returns an `HKT` of `[]A`.
|
||||||
|
// e.g. it can turn an `[]Either[error, string]` into an `Either[error, []string]`.
|
||||||
|
//
|
||||||
|
// Sequence requires an `Applicative` of the `HKT` you are targeting, e.g. to turn an
|
||||||
|
// `[]Either[E, A]` into an `Either[E, []A]`, it needs an
|
||||||
|
// Applicative` for `Either`, to to turn an `[]Option[A]` into an `Option[ []A]`,
|
||||||
|
// it needs an `Applicative` for `Option`.
|
||||||
|
func Sequence[A, HKTA, HKTRA, HKTFRA any](
|
||||||
|
_of func([]A) HKTRA,
|
||||||
|
_map func(HKTRA, func([]A) func(A) []A) HKTFRA,
|
||||||
|
_ap func(HKTFRA, HKTA) HKTRA,
|
||||||
|
) func([]HKTA) HKTRA {
|
||||||
|
ca := F.Curry2(Append[A])
|
||||||
|
return Reduce(func(fas HKTRA, fa HKTA) HKTRA {
|
||||||
|
return _ap(
|
||||||
|
_map(fas, ca),
|
||||||
|
fa,
|
||||||
|
)
|
||||||
|
}, _of(Empty[A]()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOption returns a function to convert sequence of options into an option of a sequence
|
||||||
|
func ArrayOption[A any]() func([]O.Option[A]) O.Option[[]A] {
|
||||||
|
return Sequence(
|
||||||
|
O.Of[[]A],
|
||||||
|
O.MonadMap[[]A, func(A) []A],
|
||||||
|
O.MonadAp[A, []A],
|
||||||
|
)
|
||||||
|
}
|
16
array/sequence_test.go
Normal file
16
array/sequence_test.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSequenceOption(t *testing.T) {
|
||||||
|
seq := ArrayOption[int]()
|
||||||
|
|
||||||
|
assert.Equal(t, O.Of([]int{1, 3}), seq([]O.Option[int]{O.Of(1), O.Of(3)}))
|
||||||
|
assert.Equal(t, O.None[[]int](), seq([]O.Option[int]{O.Of(1), O.None[int]()}))
|
||||||
|
}
|
11
array/sort.go
Normal file
11
array/sort.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
G "github.com/ibm/fp-go/array/generic"
|
||||||
|
O "github.com/ibm/fp-go/ord"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sort implements a stable sort on the array given the provided ordering
|
||||||
|
func Sort[T any](ord O.Ord[T]) func(ma []T) []T {
|
||||||
|
return G.Sort[[]T](ord)
|
||||||
|
}
|
21
array/sort_test.go
Normal file
21
array/sort_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
O "github.com/ibm/fp-go/ord"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSort(t *testing.T) {
|
||||||
|
|
||||||
|
ordInt := O.FromStrictCompare[int]()
|
||||||
|
|
||||||
|
input := []int{2, 1, 3}
|
||||||
|
|
||||||
|
res := Sort(ordInt)(input)
|
||||||
|
|
||||||
|
assert.Equal(t, []int{1, 2, 3}, res)
|
||||||
|
assert.Equal(t, []int{2, 1, 3}, input)
|
||||||
|
|
||||||
|
}
|
23
array/traverse.go
Normal file
23
array/traverse.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import "github.com/ibm/fp-go/internal/array"
|
||||||
|
|
||||||
|
func Traverse[A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func([]B) HKTRB,
|
||||||
|
_map func(HKTRB, func([]B) func(B) []B) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
f func(A) HKTB) func([]A) HKTRB {
|
||||||
|
return array.Traverse[[]A](_of, _map, _ap, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadTraverse[A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func([]B) HKTRB,
|
||||||
|
_map func(HKTRB, func([]B) func(B) []B) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
ta []A,
|
||||||
|
f func(A) HKTB) HKTRB {
|
||||||
|
|
||||||
|
return array.MonadTraverse(_of, _map, _ap, ta, f)
|
||||||
|
}
|
28
array/traverse_test.go
Normal file
28
array/traverse_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArrayType = []int
|
||||||
|
|
||||||
|
func TestTraverse(t *testing.T) {
|
||||||
|
|
||||||
|
traverse := Traverse(
|
||||||
|
O.Of[ArrayType],
|
||||||
|
O.MonadMap[ArrayType, func(int) ArrayType],
|
||||||
|
O.MonadAp[int, ArrayType],
|
||||||
|
|
||||||
|
func(n int) O.Option[int] {
|
||||||
|
if n%2 == 0 {
|
||||||
|
return O.None[int]()
|
||||||
|
}
|
||||||
|
return O.Of(n)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, O.None[[]int](), traverse(ArrayType{1, 2}))
|
||||||
|
assert.Equal(t, O.Of(ArrayType{1, 3}), traverse(ArrayType{1, 3}))
|
||||||
|
}
|
21
constraints/constraints.go
Normal file
21
constraints/constraints.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package constraints
|
||||||
|
|
||||||
|
type Ordered interface {
|
||||||
|
Integer | Float | ~string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Signed interface {
|
||||||
|
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unsigned interface {
|
||||||
|
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Integer interface {
|
||||||
|
Signed | Unsigned
|
||||||
|
}
|
||||||
|
|
||||||
|
type Float interface {
|
||||||
|
~float32 | ~float64
|
||||||
|
}
|
14
either/apply.go
Normal file
14
either/apply.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApplySemigroup[E, A any](s S.Semigroup[A]) S.Semigroup[Either[E, A]] {
|
||||||
|
return S.ApplySemigroup(MonadMap[E, A, func(A) A], MonadAp[E, A, A], s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplicativeMonoid[E, A any](m M.Monoid[A]) M.Monoid[Either[E, A]] {
|
||||||
|
return M.ApplicativeMonoid(Of[E, A], MonadMap[E, A, func(A) A], MonadAp[E, A, A], m)
|
||||||
|
}
|
48
either/apply_test.go
Normal file
48
either/apply_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
M "github.com/ibm/fp-go/monoid/testing"
|
||||||
|
N "github.com/ibm/fp-go/number"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplySemigroup(t *testing.T) {
|
||||||
|
|
||||||
|
sg := ApplySemigroup[string](N.SemigroupSum[int]())
|
||||||
|
|
||||||
|
la := Left[string, int]("a")
|
||||||
|
lb := Left[string, int]("b")
|
||||||
|
r1 := Right[string](1)
|
||||||
|
r2 := Right[string](2)
|
||||||
|
r3 := Right[string](3)
|
||||||
|
|
||||||
|
assert.Equal(t, la, sg.Concat(la, lb))
|
||||||
|
assert.Equal(t, lb, sg.Concat(r1, lb))
|
||||||
|
assert.Equal(t, la, sg.Concat(la, r2))
|
||||||
|
assert.Equal(t, lb, sg.Concat(r1, lb))
|
||||||
|
assert.Equal(t, r3, sg.Concat(r1, r2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplicativeMonoid(t *testing.T) {
|
||||||
|
|
||||||
|
m := ApplicativeMonoid[string](N.MonoidSum[int]())
|
||||||
|
|
||||||
|
la := Left[string, int]("a")
|
||||||
|
lb := Left[string, int]("b")
|
||||||
|
r1 := Right[string](1)
|
||||||
|
r2 := Right[string](2)
|
||||||
|
r3 := Right[string](3)
|
||||||
|
|
||||||
|
assert.Equal(t, la, m.Concat(la, m.Empty()))
|
||||||
|
assert.Equal(t, lb, m.Concat(m.Empty(), lb))
|
||||||
|
assert.Equal(t, r1, m.Concat(r1, m.Empty()))
|
||||||
|
assert.Equal(t, r2, m.Concat(m.Empty(), r2))
|
||||||
|
assert.Equal(t, r3, m.Concat(r1, r2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplicativeMonoidLaws(t *testing.T) {
|
||||||
|
m := ApplicativeMonoid[string](N.MonoidSum[int]())
|
||||||
|
M.AssertLaws(t, m)([]Either[string, int]{Left[string, int]("a"), Right[string](1)})
|
||||||
|
}
|
61
either/curry.go
Normal file
61
either/curry.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// these function curry a golang function that returns an error into its curried version that returns an either
|
||||||
|
|
||||||
|
func Curry0[R any](f func() (R, error)) func() Either[error, R] {
|
||||||
|
return Eitherize0(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry1[T1, R any](f func(T1) (R, error)) func(T1) Either[error, R] {
|
||||||
|
return Eitherize1(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry2[T1, T2, R any](f func(T1, T2) (R, error)) func(T1) func(T2) Either[error, R] {
|
||||||
|
return F.Curry2(Eitherize2(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry3[T1, T2, T3, R any](f func(T1, T2, T3) (R, error)) func(T1) func(T2) func(T3) Either[error, R] {
|
||||||
|
return F.Curry3(Eitherize3(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) (R, error)) func(T1) func(T2) func(T3) func(T4) Either[error, R] {
|
||||||
|
return F.Curry4(Eitherize4(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry0[R any](f func() Either[error, R]) func() (R, error) {
|
||||||
|
return func() (R, error) {
|
||||||
|
return UnwrapError(f())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry1[T1, R any](f func(T1) Either[error, R]) func(T1) (R, error) {
|
||||||
|
uc := F.Uncurry1(f)
|
||||||
|
return func(t1 T1) (R, error) {
|
||||||
|
return UnwrapError(uc(t1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry2[T1, T2, R any](f func(T1) func(T2) Either[error, R]) func(T1, T2) (R, error) {
|
||||||
|
uc := F.Uncurry2(f)
|
||||||
|
return func(t1 T1, t2 T2) (R, error) {
|
||||||
|
return UnwrapError(uc(t1, t2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry3[T1, T2, T3, R any](f func(T1) func(T2) func(T3) Either[error, R]) func(T1, T2, T3) (R, error) {
|
||||||
|
uc := F.Uncurry3(f)
|
||||||
|
return func(t1 T1, t2 T2, t3 T3) (R, error) {
|
||||||
|
return UnwrapError(uc(t1, t2, t3))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry4[T1, T2, T3, T4, R any](f func(T1) func(T2) func(T3) func(T4) Either[error, R]) func(T1, T2, T3, T4) (R, error) {
|
||||||
|
uc := F.Uncurry4(f)
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error) {
|
||||||
|
return UnwrapError(uc(t1, t2, t3, t4))
|
||||||
|
}
|
||||||
|
}
|
344
either/either.go
Normal file
344
either/either.go
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
// package either implements the Either monad
|
||||||
|
//
|
||||||
|
// A data type that can be of either of two types but not both. This is
|
||||||
|
// typically used to carry an error or a return value
|
||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
E "github.com/ibm/fp-go/errors"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Of[E, A any](value A) Either[E, A] {
|
||||||
|
return F.Pipe1(value, Right[E, A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromIO[E, A any](f func() A) Either[E, A] {
|
||||||
|
return F.Pipe1(f(), Right[E, A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadAp[E, A, B any](fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] {
|
||||||
|
return fold(fab, Left[E, B], func(ab func(A) B) Either[E, B] {
|
||||||
|
return fold(fa, Left[E, B], F.Flow2(ab, Right[E, B]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ap[E, A, B any](fa Either[E, A]) func(fab Either[E, func(a A) B]) Either[E, B] {
|
||||||
|
return F.Bind2nd(MonadAp[E, A, B], fa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMap[E, A, B any](fa Either[E, A], f func(a A) B) Either[E, B] {
|
||||||
|
return MonadChain(fa, F.Flow2(f, Right[E, B]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadBiMap[E1, E2, A, B any](fa Either[E1, A], f func(E1) E2, g func(a A) B) Either[E2, B] {
|
||||||
|
return fold(fa, F.Flow2(f, Left[E2, B]), F.Flow2(g, Right[E2, B]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BiMap maps a pair of functions over the two type arguments of the bifunctor.
|
||||||
|
func BiMap[E1, E2, A, B any](f func(E1) E2, g func(a A) B) func(Either[E1, A]) Either[E2, B] {
|
||||||
|
return Fold(F.Flow2(f, Left[E2, B]), F.Flow2(g, Right[E2, B]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMapTo[E, A, B any](fa Either[E, A], b B) Either[E, B] {
|
||||||
|
return MonadMap(fa, F.Constant1[A](b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapTo[E, A, B any](b B) func(Either[E, A]) Either[E, B] {
|
||||||
|
return F.Bind2nd(MonadMapTo[E, A, B], b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMapLeft[E, A, B any](fa Either[E, A], f func(E) B) Either[B, A] {
|
||||||
|
return fold(fa, F.Flow2(f, Left[B, A]), Right[B, A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map[E, A, B any](f func(a A) B) func(fa Either[E, A]) Either[E, B] {
|
||||||
|
return Chain(F.Flow2(f, Right[E, B]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapLeft[E, A, B any](f func(E) B) func(fa Either[E, A]) Either[B, A] {
|
||||||
|
return F.Bind2nd(MonadMapLeft[E, A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChain[E, A, B any](fa Either[E, A], f func(a A) Either[E, B]) Either[E, B] {
|
||||||
|
return fold(fa, Left[E, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChainFirst[E, A, B any](ma Either[E, A], f func(a A) Either[E, B]) Either[E, A] {
|
||||||
|
return MonadChain(ma, func(a A) Either[E, A] {
|
||||||
|
return MonadMap(f(a), F.Constant1[B](a))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChainTo[E, A, B any](ma Either[E, A], mb Either[E, B]) Either[E, B] {
|
||||||
|
return mb
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChainOptionK[E, A, B any](onNone func() E, ma Either[E, A], f func(A) O.Option[B]) Either[E, B] {
|
||||||
|
return MonadChain(ma, F.Flow2(f, FromOption[E, B](onNone)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChainOptionK[E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
|
||||||
|
from := FromOption[E, B](onNone)
|
||||||
|
return func(f func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
|
||||||
|
return Chain(F.Flow2(f, from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChainTo[E, A, B any](mb Either[E, B]) func(Either[E, A]) Either[E, B] {
|
||||||
|
return F.Bind2nd(MonadChainTo[E, A, B], mb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Chain[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, B] {
|
||||||
|
return F.Bind2nd(MonadChain[E, A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChainFirst[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, A] {
|
||||||
|
return F.Bind2nd(MonadChainFirst[E, A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flatten[E, A any](mma Either[E, Either[E, A]]) Either[E, A] {
|
||||||
|
return MonadChain(mma, F.Identity[Either[E, A]])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryCatch[E, A any](f func() (A, error), onThrow func(error) E) Either[E, A] {
|
||||||
|
val, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return F.Pipe2(err, onThrow, Left[E, A])
|
||||||
|
}
|
||||||
|
return F.Pipe1(val, Right[E, A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryCatchErrorG[GA ~func() (A, error), A any](f GA) Either[error, A] {
|
||||||
|
return TryCatch(f, E.IdentityError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryCatchError[A any](f func() (A, error)) Either[error, A] {
|
||||||
|
return TryCatchErrorG(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sequence2[E, T1, T2, R any](f func(T1, T2) Either[E, R]) func(Either[E, T1], Either[E, T2]) Either[E, R] {
|
||||||
|
return func(e1 Either[E, T1], e2 Either[E, T2]) Either[E, R] {
|
||||||
|
return MonadSequence2(e1, e2, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sequence3[E, T1, T2, T3, R any](f func(T1, T2, T3) Either[E, R]) func(Either[E, T1], Either[E, T2], Either[E, T3]) Either[E, R] {
|
||||||
|
return func(e1 Either[E, T1], e2 Either[E, T2], e3 Either[E, T3]) Either[E, R] {
|
||||||
|
return MonadSequence3(e1, e2, e3, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromOption[E, A any](onNone func() E) func(O.Option[A]) Either[E, A] {
|
||||||
|
return O.Fold(F.Nullary2(onNone, Left[E, A]), Right[E, A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToOption[E, A any]() func(Either[E, A]) O.Option[A] {
|
||||||
|
return Fold(F.Ignore1[E](O.None[A]), O.Some[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromError[A any](f func(a A) error) func(A) Either[error, A] {
|
||||||
|
return func(a A) Either[error, A] {
|
||||||
|
return TryCatchError(func() (A, error) {
|
||||||
|
return a, f(a)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToError[A any](e Either[error, A]) error {
|
||||||
|
return fold(e, E.IdentityError, F.Constant1[A, error](nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize0G[GA ~func() (R, error), GB ~func() Either[error, R], R any](f GA) GB {
|
||||||
|
return F.Bind1(TryCatchErrorG[GA, R], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize0[R any](f func() (R, error)) func() Either[error, R] {
|
||||||
|
return Eitherize0G[func() (R, error), func() Either[error, R]](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize0G[GA ~func() Either[error, R], GB ~func() (R, error), R any](f GA) GB {
|
||||||
|
return func() (R, error) {
|
||||||
|
return UnwrapError(f())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize0[R any](f func() Either[error, R]) func() (R, error) {
|
||||||
|
return Uneitherize0G[func() Either[error, R], func() (R, error)](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize1G[GA ~func(T1) (R, error), GB ~func(T1) Either[error, R], T1, R any](f GA) GB {
|
||||||
|
return func(t1 T1) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize1[T1, R any](f func(T1) (R, error)) func(T1) Either[error, R] {
|
||||||
|
return Eitherize1G[func(T1) (R, error), func(T1) Either[error, R]](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize1G[GA ~func(T1) Either[error, R], GB ~func(T1) (R, error), T1, R any](f GA) GB {
|
||||||
|
return func(t1 T1) (R, error) {
|
||||||
|
return UnwrapError(f(t1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize1[T1, R any](f func(T1) Either[error, R]) func(T1) (R, error) {
|
||||||
|
return Uneitherize1G[func(T1) Either[error, R], func(T1) (R, error)](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize2G[GA ~func(t1 T1, t2 T2) (R, error), GB ~func(t1 T1, t2 T2) Either[error, R], T1, T2, R any](f GA) GB {
|
||||||
|
return func(t1 T1, t2 T2) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize2[T1, T2, R any](f func(t1 T1, t2 T2) (R, error)) func(t1 T1, t2 T2) Either[error, R] {
|
||||||
|
return Eitherize2G[func(t1 T1, t2 T2) (R, error), func(t1 T1, t2 T2) Either[error, R]](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize2G[GA ~func(T1, T2) Either[error, R], GB ~func(T1, T2) (R, error), T1, T2, R any](f GA) GB {
|
||||||
|
return func(t1 T1, t2 T2) (R, error) {
|
||||||
|
return UnwrapError(f(t1, t2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize2[T1, T2, R any](f func(T1, T2) Either[error, R]) func(T1, T2) (R, error) {
|
||||||
|
return Uneitherize2G[func(T1, T2) Either[error, R], func(T1, T2) (R, error)](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize3G[GA ~func(t1 T1, t2 T2, t3 T3) (R, error), GB ~func(t1 T1, t2 T2, t3 T3) Either[error, R], T1, T2, T3, R any](f GA) GB {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, t3)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize3[T1, T2, T3, R any](f func(t1 T1, t2 T2, t3 T3) (R, error)) func(t1 T1, t2 T2, t3 T3) Either[error, R] {
|
||||||
|
return Eitherize3G[func(t1 T1, t2 T2, t3 T3) (R, error), func(t1 T1, t2 T2, t3 T3) Either[error, R]](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize3G[GA ~func(T1, T2, T3) Either[error, R], GB ~func(T1, T2, T3) (R, error), T1, T2, T3, R any](f GA) GB {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3) (R, error) {
|
||||||
|
return UnwrapError(f(t1, t2, t3))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize3[T1, T2, T3, R any](f func(T1, T2, T3) Either[error, R]) func(T1, T2, T3) (R, error) {
|
||||||
|
return Uneitherize3G[func(T1, T2, T3) Either[error, R], func(T1, T2, T3) (R, error)](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize4G[GA ~func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error), GB ~func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R], T1, T2, T3, T4, R any](f GA) GB {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, t3, t4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Eitherize4[T1, T2, T3, T4, R any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error)) func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R] {
|
||||||
|
return Eitherize4G[func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error), func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R]](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize4G[GA ~func(T1, T2, T3, T4) Either[error, R], GB ~func(T1, T2, T3, T4) (R, error), T1, T2, T3, T4, R any](f GA) GB {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error) {
|
||||||
|
return UnwrapError(f(t1, t2, t3, t4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uneitherize4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) Either[error, R]) func(T1, T2, T3, T4) (R, error) {
|
||||||
|
return Uneitherize4G[func(T1, T2, T3, T4) Either[error, R], func(T1, T2, T3, T4) (R, error)](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
|
||||||
|
return fold(ma, onLeft, onRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fold[E, A, B any](onLeft func(E) B, onRight func(A) B) func(Either[E, A]) B {
|
||||||
|
return func(ma Either[E, A]) B {
|
||||||
|
return fold(ma, onLeft, onRight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnwrapError[A any](ma Either[error, A]) (A, error) {
|
||||||
|
return Unwrap[error](ma)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromPredicate[E, A any](pred func(A) bool, onFalse func(A) E) func(A) Either[E, A] {
|
||||||
|
return func(a A) Either[E, A] {
|
||||||
|
if pred(a) {
|
||||||
|
return Right[E](a)
|
||||||
|
}
|
||||||
|
return Left[E, A](onFalse(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromNillable[E, A any](e E) func(*A) Either[E, *A] {
|
||||||
|
return FromPredicate(F.IsNonNil[A], F.Constant1[*A](e))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOrElse[E, A any](onLeft func(E) A) func(Either[E, A]) A {
|
||||||
|
return Fold(onLeft, F.Identity[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Reduce[E, A, B any](f func(B, A) B, initial B) func(Either[E, A]) B {
|
||||||
|
return Fold(
|
||||||
|
F.Constant1[E](initial),
|
||||||
|
F.Bind1st(f, initial),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AltW[E, E1, A any](that func() Either[E1, A]) func(Either[E, A]) Either[E1, A] {
|
||||||
|
return Fold(F.Ignore1[E](that), Right[E1, A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Alt[E, A any](that func() Either[E, A]) func(Either[E, A]) Either[E, A] {
|
||||||
|
return AltW[E](that)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OrElse[E, A any](onLeft func(e E) Either[E, A]) func(Either[E, A]) Either[E, A] {
|
||||||
|
return Fold(onLeft, Of[E, A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToType[E, A any](onError func(any) E) func(any) Either[E, A] {
|
||||||
|
return func(value any) Either[E, A] {
|
||||||
|
return F.Pipe2(
|
||||||
|
value,
|
||||||
|
O.ToType[A],
|
||||||
|
O.Fold(F.Nullary3(F.Constant(value), onError, Left[E, A]), Right[E, A]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Memoize[E, A any](val Either[E, A]) Either[E, A] {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadSequence2[E, T1, T2, R any](e1 Either[E, T1], e2 Either[E, T2], f func(T1, T2) Either[E, R]) Either[E, R] {
|
||||||
|
return fold(e1, Left[E, R], func(t1 T1) Either[E, R] {
|
||||||
|
return fold(e2, Left[E, R], func(t2 T2) Either[E, R] {
|
||||||
|
return f(t1, t2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadSequence3[E, T1, T2, T3, R any](e1 Either[E, T1], e2 Either[E, T2], e3 Either[E, T3], f func(T1, T2, T3) Either[E, R]) Either[E, R] {
|
||||||
|
return fold(e1, Left[E, R], func(t1 T1) Either[E, R] {
|
||||||
|
return fold(e2, Left[E, R], func(t2 T2) Either[E, R] {
|
||||||
|
return fold(e3, Left[E, R], func(t3 T3) Either[E, R] {
|
||||||
|
return f(t1, t2, t3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap changes the order of type parameters
|
||||||
|
func Swap[E, A any](val Either[E, A]) Either[A, E] {
|
||||||
|
return fold(val, Right[A, E], Left[A, E])
|
||||||
|
}
|
96
either/either_test.go
Normal file
96
either/either_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/ibm/fp-go/internal/utils"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
S "github.com/ibm/fp-go/string"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsLeft(t *testing.T) {
|
||||||
|
err := errors.New("Some error")
|
||||||
|
withError := Left[error, string](err)
|
||||||
|
|
||||||
|
assert.True(t, IsLeft(withError))
|
||||||
|
assert.False(t, IsRight(withError))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsRight(t *testing.T) {
|
||||||
|
noError := Right[error]("Carsten")
|
||||||
|
|
||||||
|
assert.True(t, IsRight(noError))
|
||||||
|
assert.False(t, IsLeft(noError))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapEither(t *testing.T) {
|
||||||
|
|
||||||
|
assert.Equal(t, F.Pipe1(Right[error]("abc"), Map[error](utils.StringLen)), Right[error](3))
|
||||||
|
|
||||||
|
val2 := F.Pipe1(Left[string, string]("s"), Map[string](utils.StringLen))
|
||||||
|
exp2 := Left[string, int]("s")
|
||||||
|
|
||||||
|
assert.Equal(t, val2, exp2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnwrapError(t *testing.T) {
|
||||||
|
a := ""
|
||||||
|
err := errors.New("Some error")
|
||||||
|
withError := Left[error, string](err)
|
||||||
|
|
||||||
|
res, extracted := UnwrapError(withError)
|
||||||
|
assert.Equal(t, a, res)
|
||||||
|
assert.Equal(t, extracted, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduce(t *testing.T) {
|
||||||
|
|
||||||
|
s := S.Semigroup()
|
||||||
|
|
||||||
|
assert.Equal(t, "foobar", F.Pipe1(Right[string]("bar"), Reduce[string](s.Concat, "foo")))
|
||||||
|
assert.Equal(t, "foo", F.Pipe1(Left[string, string]("bar"), Reduce[string](s.Concat, "foo")))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAp(t *testing.T) {
|
||||||
|
f := S.Size
|
||||||
|
|
||||||
|
assert.Equal(t, Right[string](3), F.Pipe1(Right[string](f), Ap[string, string, int](Right[string]("abc"))))
|
||||||
|
assert.Equal(t, Left[string, int]("maError"), F.Pipe1(Right[string](f), Ap[string, string, int](Left[string, string]("maError"))))
|
||||||
|
assert.Equal(t, Left[string, int]("mabError"), F.Pipe1(Left[string, func(string) int]("mabError"), Ap[string, string, int](Left[string, string]("maError"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlt(t *testing.T) {
|
||||||
|
assert.Equal(t, Right[string](1), F.Pipe1(Right[string](1), Alt(F.Constant(Right[string](2)))))
|
||||||
|
assert.Equal(t, Right[string](1), F.Pipe1(Right[string](1), Alt(F.Constant(Left[string, int]("a")))))
|
||||||
|
assert.Equal(t, Right[string](2), F.Pipe1(Left[string, int]("b"), Alt(F.Constant(Right[string](2)))))
|
||||||
|
assert.Equal(t, Left[string, int]("b"), F.Pipe1(Left[string, int]("a"), Alt(F.Constant(Left[string, int]("b")))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainFirst(t *testing.T) {
|
||||||
|
f := F.Flow2(S.Size, Right[string, int])
|
||||||
|
|
||||||
|
assert.Equal(t, Right[string]("abc"), F.Pipe1(Right[string]("abc"), ChainFirst(f)))
|
||||||
|
assert.Equal(t, Left[string, string]("maError"), F.Pipe1(Left[string, string]("maError"), ChainFirst(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainOptionK(t *testing.T) {
|
||||||
|
f := ChainOptionK[string, int, int](F.Constant("a"))(func(n int) O.Option[int] {
|
||||||
|
if n > 0 {
|
||||||
|
return O.Some(n)
|
||||||
|
}
|
||||||
|
return O.None[int]()
|
||||||
|
})
|
||||||
|
assert.Equal(t, Right[string](1), f(Right[string](1)))
|
||||||
|
assert.Equal(t, Left[string, int]("a"), f(Right[string](-1)))
|
||||||
|
assert.Equal(t, Left[string, int]("b"), f(Left[string, int]("b")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromOption(t *testing.T) {
|
||||||
|
assert.Equal(t, Left[string, int]("none"), FromOption[string, int](F.Constant("none"))(O.None[int]()))
|
||||||
|
assert.Equal(t, Right[string](1), FromOption[string, int](F.Constant("none"))(O.Some(1)))
|
||||||
|
}
|
25
either/eq.go
Normal file
25
either/eq.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
EQ "github.com/ibm/fp-go/eq"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constructs an equal predicate for an `Either`
|
||||||
|
func Eq[E, A any](e EQ.Eq[E], a EQ.Eq[A]) EQ.Eq[Either[E, A]] {
|
||||||
|
// some convenient shortcuts
|
||||||
|
eqa := F.Curry2(a.Equals)
|
||||||
|
eqe := F.Curry2(e.Equals)
|
||||||
|
|
||||||
|
fca := F.Bind2nd(Fold[E, A, bool], F.Constant1[A](false))
|
||||||
|
fce := F.Bind1st(Fold[E, A, bool], F.Constant1[E](false))
|
||||||
|
|
||||||
|
fld := Fold(F.Flow2(eqe, fca), F.Flow2(eqa, fce))
|
||||||
|
|
||||||
|
return EQ.FromEquals(F.Uncurry2(fld))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStrictEquals constructs an `Eq` from the canonical comparison function
|
||||||
|
func FromStrictEquals[E, A comparable]() EQ.Eq[Either[E, A]] {
|
||||||
|
return Eq(EQ.FromStrictEquals[E](), EQ.FromStrictEquals[A]())
|
||||||
|
}
|
30
either/eq_test.go
Normal file
30
either/eq_test.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEq(t *testing.T) {
|
||||||
|
|
||||||
|
r1 := Of[string](1)
|
||||||
|
r2 := Of[string](1)
|
||||||
|
r3 := Of[string](2)
|
||||||
|
|
||||||
|
e1 := Left[string, int]("a")
|
||||||
|
e2 := Left[string, int]("a")
|
||||||
|
e3 := Left[string, int]("b")
|
||||||
|
|
||||||
|
eq := FromStrictEquals[string, int]()
|
||||||
|
|
||||||
|
assert.True(t, eq.Equals(r1, r1))
|
||||||
|
assert.True(t, eq.Equals(r1, r2))
|
||||||
|
assert.False(t, eq.Equals(r1, r3))
|
||||||
|
assert.False(t, eq.Equals(r1, e1))
|
||||||
|
|
||||||
|
assert.True(t, eq.Equals(e1, e1))
|
||||||
|
assert.True(t, eq.Equals(e1, e2))
|
||||||
|
assert.False(t, eq.Equals(e1, e3))
|
||||||
|
assert.False(t, eq.Equals(e2, r2))
|
||||||
|
}
|
23
either/exec/exec.go
Normal file
23
either/exec/exec.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package Exec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
E "github.com/ibm/fp-go/either"
|
||||||
|
"github.com/ibm/fp-go/exec"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
GE "github.com/ibm/fp-go/internal/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Command executes a command
|
||||||
|
// use this version if the command does not produce any side effect, i.e. if the output is uniquely determined by by the input
|
||||||
|
// typically you'd rather use the IOEither version of the command
|
||||||
|
Command = F.Curry3(command)
|
||||||
|
)
|
||||||
|
|
||||||
|
func command(name string, args []string, in []byte) E.Either[error, exec.CommandOutput] {
|
||||||
|
return E.TryCatchError(func() (exec.CommandOutput, error) {
|
||||||
|
return GE.Exec(context.Background(), name, args, in)
|
||||||
|
})
|
||||||
|
}
|
36
either/http/request.go
Normal file
36
either/http/request.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package Http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
E "github.com/ibm/fp-go/either"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
PostRequest = bodyRequest("POST")
|
||||||
|
PutRequest = bodyRequest("PUT")
|
||||||
|
|
||||||
|
GetRequest = noBodyRequest("GET")
|
||||||
|
DeleteRequest = noBodyRequest("DELETE")
|
||||||
|
OptionsRequest = noBodyRequest("OPTIONS")
|
||||||
|
HeadRequest = noBodyRequest("HEAD")
|
||||||
|
)
|
||||||
|
|
||||||
|
func bodyRequest(method string) func(string) func([]byte) E.Either[error, *http.Request] {
|
||||||
|
return func(url string) func([]byte) E.Either[error, *http.Request] {
|
||||||
|
return func(body []byte) E.Either[error, *http.Request] {
|
||||||
|
return E.TryCatchError(func() (*http.Request, error) {
|
||||||
|
return http.NewRequest(method, url, bytes.NewReader(body))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func noBodyRequest(method string) func(string) E.Either[error, *http.Request] {
|
||||||
|
return func(url string) E.Either[error, *http.Request] {
|
||||||
|
return E.TryCatchError(func() (*http.Request, error) {
|
||||||
|
return http.NewRequest(method, url, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
55
either/legacy.go
Normal file
55
either/legacy.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type EitherTag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LeftTag EitherTag = iota
|
||||||
|
RightTag
|
||||||
|
)
|
||||||
|
|
||||||
|
// Either defines a data structure that logically holds either an E or an A. The tag discriminates the cases
|
||||||
|
type Either[E, A any] struct {
|
||||||
|
Tag EitherTag
|
||||||
|
Left E
|
||||||
|
Right A
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints some debug info for the object
|
||||||
|
func (s Either[E, A]) String() string {
|
||||||
|
switch s.Tag {
|
||||||
|
case LeftTag:
|
||||||
|
return fmt.Sprintf("Left[%T, %T](%v)", s.Left, s.Right, s.Left)
|
||||||
|
case RightTag:
|
||||||
|
return fmt.Sprintf("Right[%T, %T](%v)", s.Left, s.Right, s.Right)
|
||||||
|
}
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsLeft[E, A any](val Either[E, A]) bool {
|
||||||
|
return val.Tag == LeftTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRight[E, A any](val Either[E, A]) bool {
|
||||||
|
return val.Tag == RightTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func Left[E, A any](value E) Either[E, A] {
|
||||||
|
return Either[E, A]{Tag: LeftTag, Left: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Right[E, A any](value A) Either[E, A] {
|
||||||
|
return Either[E, A]{Tag: RightTag, Right: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
|
||||||
|
if IsLeft(ma) {
|
||||||
|
return onLeft(ma.Left)
|
||||||
|
}
|
||||||
|
return onRight(ma.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||||
|
return ma.Right, ma.Left
|
||||||
|
}
|
33
either/logger.go
Normal file
33
either/logger.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
L "github.com/ibm/fp-go/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func _log[E, A any](left func(string, ...any), right func(string, ...any), prefix string) func(Either[E, A]) Either[E, A] {
|
||||||
|
return Fold(
|
||||||
|
func(e E) Either[E, A] {
|
||||||
|
left("%s: %v", prefix, e)
|
||||||
|
return Left[E, A](e)
|
||||||
|
},
|
||||||
|
func(a A) Either[E, A] {
|
||||||
|
right("%s: %v", prefix, a)
|
||||||
|
return Right[E](a)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Logger[E, A any](loggers ...*log.Logger) func(string) func(Either[E, A]) Either[E, A] {
|
||||||
|
left, right := L.LoggingCallbacks(loggers...)
|
||||||
|
return func(prefix string) func(Either[E, A]) Either[E, A] {
|
||||||
|
delegate := _log[E, A](left, right, prefix)
|
||||||
|
return func(ma Either[E, A]) Either[E, A] {
|
||||||
|
return F.Pipe1(
|
||||||
|
delegate(ma),
|
||||||
|
ChainTo[E, A](ma),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
either/logger_test.go
Normal file
22
either/logger_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogger(t *testing.T) {
|
||||||
|
|
||||||
|
l := Logger[error, string]()
|
||||||
|
|
||||||
|
r := Right[error]("test")
|
||||||
|
|
||||||
|
res := F.Pipe1(
|
||||||
|
r,
|
||||||
|
l("out"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, r, res)
|
||||||
|
}
|
69
either/modern._go
Normal file
69
either/modern._go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
//go:build disabled
|
||||||
|
|
||||||
|
package either
|
||||||
|
|
||||||
|
// Either will either be E or A
|
||||||
|
type Either[E, A any] interface {
|
||||||
|
fmt.Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
type left[E any] struct {
|
||||||
|
e E
|
||||||
|
}
|
||||||
|
|
||||||
|
func (left[E]) IsLeft() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type right[A any] struct {
|
||||||
|
a A
|
||||||
|
}
|
||||||
|
|
||||||
|
func (right[A]) IsLeft() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l left[E]) String() string {
|
||||||
|
return fmt.Sprintf("Left[%v]", l.e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r right[A]) String() string {
|
||||||
|
return fmt.Sprintf("Right[%v]", r.a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsLeft[E, A any](val Either[E, A]) bool {
|
||||||
|
switch any(val).(type) {
|
||||||
|
case left[E]:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRight[E, A any](val Either[E, A]) bool {
|
||||||
|
return !IsLeft(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Left[E, A any](value E) Either[E, A] {
|
||||||
|
return left[E]{e: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Right[E, A any](value A) Either[E, A] {
|
||||||
|
return right[A]{a: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
|
||||||
|
if IsLeft(ma) {
|
||||||
|
return onLeft(ma.(left[E]).e)
|
||||||
|
}
|
||||||
|
return onRight(ma.(right[A]).a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||||
|
if IsLeft(ma) {
|
||||||
|
var a A
|
||||||
|
return a, ma.(left[E]).e
|
||||||
|
}
|
||||||
|
var E e
|
||||||
|
return ma.(right[A]).a, e
|
||||||
|
}
|
30
either/record.go
Normal file
30
either/record.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
RR "github.com/ibm/fp-go/internal/record"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraverseRecord transforms a record of options into an option of a record
|
||||||
|
func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B any](f func(A) Either[E, B]) func(GA) Either[E, GB] {
|
||||||
|
return RR.Traverse[GA](
|
||||||
|
Of[E, GB],
|
||||||
|
MonadMap[E, GB, func(B) GB],
|
||||||
|
MonadAp[E, B, GB],
|
||||||
|
f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraverseRecord transforms a record of eithers into an either of a record
|
||||||
|
func TraverseRecord[K comparable, E, A, B any](f func(A) Either[E, B]) func(map[K]A) Either[E, map[K]B] {
|
||||||
|
return TraverseRecordG[map[K]A, map[K]B](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Either[E, A], K comparable, E, A any](ma GOA) Either[E, GA] {
|
||||||
|
return TraverseRecordG[GOA, GA](F.Identity[Either[E, A]])(ma)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SequenceRecord converts a homogeneous sequence of either into an either of sequence
|
||||||
|
func SequenceRecord[K comparable, E, A any](ma map[K]Either[E, A]) Either[E, map[K]A] {
|
||||||
|
return SequenceRecordG[map[K]A](ma)
|
||||||
|
}
|
29
either/resource.go
Normal file
29
either/resource.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// constructs a function that creates a resource, then operates on it and then releases the resource
|
||||||
|
func WithResource[E, R, A any](onCreate func() Either[E, R], onRelease func(R) Either[E, any]) func(func(R) Either[E, A]) Either[E, A] {
|
||||||
|
|
||||||
|
return func(f func(R) Either[E, A]) Either[E, A] {
|
||||||
|
return MonadChain(
|
||||||
|
onCreate(), func(r R) Either[E, A] {
|
||||||
|
// run the code and make sure to release as quickly as possible
|
||||||
|
res := f(r)
|
||||||
|
released := onRelease(r)
|
||||||
|
// handle the errors
|
||||||
|
return fold(
|
||||||
|
res,
|
||||||
|
Left[E, A],
|
||||||
|
func(a A) Either[E, A] {
|
||||||
|
return F.Pipe1(
|
||||||
|
released,
|
||||||
|
MapTo[E, any](a),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
39
either/resource_test.go
Normal file
39
either/resource_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWithResource(t *testing.T) {
|
||||||
|
onCreate := func() Either[error, *os.File] {
|
||||||
|
return TryCatchError(func() (*os.File, error) {
|
||||||
|
return os.CreateTemp("", "*")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onDelete := F.Flow2(
|
||||||
|
func(f *os.File) Either[error, string] {
|
||||||
|
return TryCatchError(func() (string, error) {
|
||||||
|
return f.Name(), f.Close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Chain(func(name string) Either[error, any] {
|
||||||
|
return TryCatchError(func() (any, error) {
|
||||||
|
return name, os.Remove(name)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
onHandler := func(f *os.File) Either[error, string] {
|
||||||
|
return Of[error](f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile := WithResource[error, *os.File, string](onCreate, onDelete)
|
||||||
|
|
||||||
|
resE := tempFile(onHandler)
|
||||||
|
|
||||||
|
assert.True(t, IsRight(resE))
|
||||||
|
}
|
45
either/sequence.go
Normal file
45
either/sequence.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
Apply "github.com/ibm/fp-go/apply"
|
||||||
|
T "github.com/ibm/fp-go/tuple"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
|
||||||
|
|
||||||
|
func SequenceT1[E, A any](a Either[E, A]) Either[E, T.Tuple1[A]] {
|
||||||
|
return Apply.SequenceT1(
|
||||||
|
MonadMap[E, A, T.Tuple1[A]],
|
||||||
|
a,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SequenceT2[E, A, B any](a Either[E, A], b Either[E, B]) Either[E, T.Tuple2[A, B]] {
|
||||||
|
return Apply.SequenceT2(
|
||||||
|
MonadMap[E, A, func(B) T.Tuple2[A, B]],
|
||||||
|
MonadAp[E, B, T.Tuple2[A, B]],
|
||||||
|
|
||||||
|
a, b,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SequenceT3[E, A, B, C any](a Either[E, A], b Either[E, B], c Either[E, C]) Either[E, T.Tuple3[A, B, C]] {
|
||||||
|
return Apply.SequenceT3(
|
||||||
|
MonadMap[E, A, func(B) func(C) T.Tuple3[A, B, C]],
|
||||||
|
MonadAp[E, B, func(C) T.Tuple3[A, B, C]],
|
||||||
|
MonadAp[E, C, T.Tuple3[A, B, C]],
|
||||||
|
|
||||||
|
a, b, c,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SequenceT4[E, A, B, C, D any](a Either[E, A], b Either[E, B], c Either[E, C], d Either[E, D]) Either[E, T.Tuple4[A, B, C, D]] {
|
||||||
|
return Apply.SequenceT4(
|
||||||
|
MonadMap[E, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]],
|
||||||
|
MonadAp[E, B, func(C) func(D) T.Tuple4[A, B, C, D]],
|
||||||
|
MonadAp[E, C, func(D) T.Tuple4[A, B, C, D]],
|
||||||
|
MonadAp[E, D, T.Tuple4[A, B, C, D]],
|
||||||
|
|
||||||
|
a, b, c, d,
|
||||||
|
)
|
||||||
|
}
|
60
either/testing/laws.go
Normal file
60
either/testing/laws.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ET "github.com/ibm/fp-go/either"
|
||||||
|
EQ "github.com/ibm/fp-go/eq"
|
||||||
|
L "github.com/ibm/fp-go/internal/monad/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AssertLaws asserts the apply monad laws for the `Either` monad
|
||||||
|
func AssertLaws[E, A, B, C any](t *testing.T,
|
||||||
|
eqe EQ.Eq[E],
|
||||||
|
eqa EQ.Eq[A],
|
||||||
|
eqb EQ.Eq[B],
|
||||||
|
eqc EQ.Eq[C],
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(a A) bool {
|
||||||
|
|
||||||
|
return L.AssertLaws(t,
|
||||||
|
ET.Eq(eqe, eqa),
|
||||||
|
ET.Eq(eqe, eqb),
|
||||||
|
ET.Eq(eqe, eqc),
|
||||||
|
|
||||||
|
ET.Of[E, A],
|
||||||
|
ET.Of[E, B],
|
||||||
|
ET.Of[E, C],
|
||||||
|
|
||||||
|
ET.Of[E, func(A) A],
|
||||||
|
ET.Of[E, func(A) B],
|
||||||
|
ET.Of[E, func(B) C],
|
||||||
|
ET.Of[E, func(func(A) B) B],
|
||||||
|
|
||||||
|
ET.MonadMap[E, A, A],
|
||||||
|
ET.MonadMap[E, A, B],
|
||||||
|
ET.MonadMap[E, A, C],
|
||||||
|
ET.MonadMap[E, B, C],
|
||||||
|
|
||||||
|
ET.MonadMap[E, func(B) C, func(func(A) B) func(A) C],
|
||||||
|
|
||||||
|
ET.MonadChain[E, A, A],
|
||||||
|
ET.MonadChain[E, A, B],
|
||||||
|
ET.MonadChain[E, A, C],
|
||||||
|
ET.MonadChain[E, B, C],
|
||||||
|
|
||||||
|
ET.MonadAp[E, A, A],
|
||||||
|
ET.MonadAp[E, A, B],
|
||||||
|
ET.MonadAp[E, B, C],
|
||||||
|
ET.MonadAp[E, A, C],
|
||||||
|
|
||||||
|
ET.MonadAp[E, func(A) B, B],
|
||||||
|
ET.MonadAp[E, func(A) B, func(A) C],
|
||||||
|
|
||||||
|
ab,
|
||||||
|
bc,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
33
either/testing/laws_test.go
Normal file
33
either/testing/laws_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
EQ "github.com/ibm/fp-go/eq"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMonadLaws(t *testing.T) {
|
||||||
|
// some comparison
|
||||||
|
eqe := EQ.FromStrictEquals[string]()
|
||||||
|
eqa := EQ.FromStrictEquals[bool]()
|
||||||
|
eqb := EQ.FromStrictEquals[int]()
|
||||||
|
eqc := EQ.FromStrictEquals[string]()
|
||||||
|
|
||||||
|
ab := func(a bool) int {
|
||||||
|
if a {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
bc := func(b int) string {
|
||||||
|
return fmt.Sprintf("value %d", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
laws := AssertLaws(t, eqe, eqa, eqb, eqc, ab, bc)
|
||||||
|
|
||||||
|
assert.True(t, laws(true))
|
||||||
|
assert.True(t, laws(false))
|
||||||
|
}
|
54
either/traverse.go
Normal file
54
either/traverse.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||||
|
|
||||||
|
HKTRB = HKT<Either[B]>
|
||||||
|
HKTA = HKT<A>
|
||||||
|
HKTB = HKT<B>
|
||||||
|
*/
|
||||||
|
func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||||
|
_of func(Either[E, B]) HKTRB,
|
||||||
|
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||||
|
) func(Either[E, A], func(A) HKTB) HKTRB {
|
||||||
|
|
||||||
|
left := F.Flow2(Left[E, B], _of)
|
||||||
|
right := F.Bind2nd(_map, Right[E, B])
|
||||||
|
|
||||||
|
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
|
||||||
|
return fold(ta,
|
||||||
|
left,
|
||||||
|
F.Flow2(f, right),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||||
|
_of func(Either[E, B]) HKTRB,
|
||||||
|
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||||
|
) func(func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||||
|
delegate := traverse[E, A, B, HKTA](_of, _map)
|
||||||
|
return func(f func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||||
|
return F.Bind2nd(delegate, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||||
|
|
||||||
|
HKTRA = HKT<Either[A]>
|
||||||
|
HKTA = HKT<A>
|
||||||
|
HKTB = HKT<B>
|
||||||
|
*/
|
||||||
|
func Sequence[E, A, HKTA, HKTRA any](
|
||||||
|
_of func(Either[E, A]) HKTRA,
|
||||||
|
_map func(HKTA, func(A) Either[E, A]) HKTRA,
|
||||||
|
) func(Either[E, HKTA]) HKTRA {
|
||||||
|
return Fold(F.Flow2(Left[E, A], _of), F.Bind2nd(_map, Right[E, A]))
|
||||||
|
}
|
38
either/traverse_test.go
Normal file
38
either/traverse_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTraverse(t *testing.T) {
|
||||||
|
f := func(n int) O.Option[int] {
|
||||||
|
if n >= 2 {
|
||||||
|
return O.Of(n)
|
||||||
|
}
|
||||||
|
return O.None[int]()
|
||||||
|
}
|
||||||
|
trav := Traverse[string, int, int, O.Option[Either[string, int]]](
|
||||||
|
O.Of[Either[string, int]],
|
||||||
|
O.MonadMap[int, Either[string, int]],
|
||||||
|
)(f)
|
||||||
|
|
||||||
|
assert.Equal(t, O.Of(Left[string, int]("a")), F.Pipe1(Left[string, int]("a"), trav))
|
||||||
|
assert.Equal(t, O.None[Either[string, int]](), F.Pipe1(Right[string](1), trav))
|
||||||
|
assert.Equal(t, O.Of(Right[string](3)), F.Pipe1(Right[string](3), trav))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequence(t *testing.T) {
|
||||||
|
|
||||||
|
seq := Sequence(
|
||||||
|
O.Of[Either[string, int]],
|
||||||
|
O.MonadMap[int, Either[string, int]],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1))))
|
||||||
|
assert.Equal(t, O.Of(Left[string, int]("a")), seq(Left[string, O.Option[int]]("a")))
|
||||||
|
assert.Equal(t, O.None[Either[string, int]](), seq(Right[string](O.None[int]())))
|
||||||
|
}
|
81
either/variadic.go
Normal file
81
either/variadic.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package either
|
||||||
|
|
||||||
|
func Variadic0[V, R any](f func([]V) (R, error)) func(...V) Either[error, R] {
|
||||||
|
return func(v ...V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic1[T1, V, R any](f func(T1, []V) (R, error)) func(T1, ...V) Either[error, R] {
|
||||||
|
return func(t1 T1, v ...V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic2[T1, T2, V, R any](f func(T1, T2, []V) (R, error)) func(T1, T2, ...V) Either[error, R] {
|
||||||
|
return func(t1 T1, t2 T2, v ...V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, []V) (R, error)) func(T1, T2, T3, ...V) Either[error, R] {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, v ...V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, t3, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, []V) (R, error)) func(T1, T2, T3, T4, ...V) Either[error, R] {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4, v ...V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, t3, t4, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic0[V, R any](f func(...V) (R, error)) func([]V) Either[error, R] {
|
||||||
|
return func(v []V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic1[T1, V, R any](f func(T1, ...V) (R, error)) func(T1, []V) Either[error, R] {
|
||||||
|
return func(t1 T1, v []V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic2[T1, T2, V, R any](f func(T1, T2, ...V) (R, error)) func(T1, T2, []V) Either[error, R] {
|
||||||
|
return func(t1 T1, t2 T2, v []V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, ...V) (R, error)) func(T1, T2, T3, []V) Either[error, R] {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, v []V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, t3, v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, ...V) (R, error)) func(T1, T2, T3, T4, []V) Either[error, R] {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4, v []V) Either[error, R] {
|
||||||
|
return TryCatchError(func() (R, error) {
|
||||||
|
return f(t1, t2, t3, t4, v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
11
eq/contramap.go
Normal file
11
eq/contramap.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package eq
|
||||||
|
|
||||||
|
// Contramap implements an Equals predicate based on a mapping
|
||||||
|
func Contramap[A, B any](f func(b B) A) func(Eq[A]) Eq[B] {
|
||||||
|
return func(fa Eq[A]) Eq[B] {
|
||||||
|
equals := fa.Equals
|
||||||
|
return FromEquals(func(x, y B) bool {
|
||||||
|
return equals(f(x), f(y))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
43
eq/eq.go
Normal file
43
eq/eq.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package eq
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Eq[T any] interface {
|
||||||
|
Equals(x, y T) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type eq[T any] struct {
|
||||||
|
c func(x, y T) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self eq[T]) Equals(x, y T) bool {
|
||||||
|
return self.c(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func strictEq[A comparable](a, b A) bool {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStrictEquals constructs an `Eq` from the canonical comparison function
|
||||||
|
func FromStrictEquals[T comparable]() Eq[T] {
|
||||||
|
return FromEquals(strictEq[T])
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEquals constructs an `Eq` from the comparison function
|
||||||
|
func FromEquals[T any](c func(x, y T) bool) Eq[T] {
|
||||||
|
return eq[T]{c: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty returns the equals predicate that is always true
|
||||||
|
func Empty[T any]() Eq[T] {
|
||||||
|
return FromEquals(F.Constant2[T, T](true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns a predicate to test if one value equals the other under an equals predicate
|
||||||
|
func Equals[T any](eq Eq[T]) func(T) func(T) bool {
|
||||||
|
return func(other T) func(T) bool {
|
||||||
|
return F.Bind2nd(eq.Equals, other)
|
||||||
|
}
|
||||||
|
}
|
18
eq/monoid.go
Normal file
18
eq/monoid.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package eq
|
||||||
|
|
||||||
|
import (
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Semigroup[A any]() S.Semigroup[Eq[A]] {
|
||||||
|
return S.MakeSemigroup(func(x, y Eq[A]) Eq[A] {
|
||||||
|
return FromEquals(func(a, b A) bool {
|
||||||
|
return x.Equals(a, b) && y.Equals(a, b)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Monoid[A any]() M.Monoid[Eq[A]] {
|
||||||
|
return M.MakeMonoid(Semigroup[A]().Concat, Empty[A]())
|
||||||
|
}
|
13
eq/testing/eq.go
Normal file
13
eq/testing/eq.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
EQ "github.com/ibm/fp-go/eq"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Eq implements the equal operation based on `ObjectsAreEqualValues` from the assertion library
|
||||||
|
func Eq[A any]() EQ.Eq[A] {
|
||||||
|
return EQ.FromEquals(func(l, r A) bool {
|
||||||
|
return assert.ObjectsAreEqualValues(l, r)
|
||||||
|
})
|
||||||
|
}
|
16
errors/conv.go
Normal file
16
errors/conv.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
// As tries to extract the error of desired type from the given error
|
||||||
|
func As[A error]() func(error) O.Option[A] {
|
||||||
|
return O.FromValidation(func(err error) (A, bool) {
|
||||||
|
var a A
|
||||||
|
ok := errors.As(err, &a)
|
||||||
|
return a, ok
|
||||||
|
})
|
||||||
|
}
|
39
errors/conv_test.go
Normal file
39
errors/conv_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyError struct{}
|
||||||
|
|
||||||
|
func (m *MyError) Error() string {
|
||||||
|
return "boom"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAs(t *testing.T) {
|
||||||
|
root := &MyError{}
|
||||||
|
err := fmt.Errorf("This is my custom error, %w", root)
|
||||||
|
|
||||||
|
errO := F.Pipe1(
|
||||||
|
err,
|
||||||
|
As[*MyError](),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, O.Of(root), errO)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotAs(t *testing.T) {
|
||||||
|
err := fmt.Errorf("This is my custom error")
|
||||||
|
|
||||||
|
errO := F.Pipe1(
|
||||||
|
err,
|
||||||
|
As[*MyError](),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, O.None[*MyError](), errO)
|
||||||
|
}
|
7
errors/identity.go
Normal file
7
errors/identity.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
var IdentityError = F.Identity[error]
|
18
errors/message_test.go
Normal file
18
errors/message_test.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOnError(t *testing.T) {
|
||||||
|
onError := OnError("Failed to process [%s]", "filename")
|
||||||
|
|
||||||
|
err := fmt.Errorf("Cause")
|
||||||
|
|
||||||
|
err1 := onError(err)
|
||||||
|
|
||||||
|
assert.Equal(t, "Failed to process [filename], Caused By: Cause", err1.Error())
|
||||||
|
}
|
23
errors/messages.go
Normal file
23
errors/messages.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
A "github.com/ibm/fp-go/array"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OnNone generates a nullary function that produces a formatted error
|
||||||
|
func OnNone(msg string, args ...any) func() error {
|
||||||
|
return func() error {
|
||||||
|
return fmt.Errorf(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnError generates a unary function that produces a formatted error. The argument
|
||||||
|
// to that function is the root cause of the error and the message will be augmented with
|
||||||
|
// a format string containing %w
|
||||||
|
func OnError(msg string, args ...any) func(error) error {
|
||||||
|
return func(err error) error {
|
||||||
|
return fmt.Errorf(msg+", Caused By: %w", A.ArrayConcatAll(args, A.Of[any](err))...)
|
||||||
|
}
|
||||||
|
}
|
15
exec/exec.go
Normal file
15
exec/exec.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package exec
|
||||||
|
|
||||||
|
import (
|
||||||
|
T "github.com/ibm/fp-go/tuple"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// command output
|
||||||
|
CommandOutput = T.Tuple2[[]byte, []byte]
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
StdOut = T.First[[]byte, []byte]
|
||||||
|
StdErr = T.Second[[]byte, []byte]
|
||||||
|
)
|
52
function/bind.go
Normal file
52
function/bind.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
func Bind1st[T1, T2, R any](f func(T1, T2) R, t1 T1) func(T2) R {
|
||||||
|
return func(t2 T2) R {
|
||||||
|
return f(t1, t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func Bind2nd[T1, T2, R any](f func(T1, T2) R, t2 T2) func(T1) R {
|
||||||
|
return func(t1 T1) R {
|
||||||
|
return f(t1, t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bind1[T1, R any](f func(T1) R, t1 T1) func() R {
|
||||||
|
return func() R {
|
||||||
|
return f(t1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bind2[T1, T2, R any](f func(T1, T2) R, t1 T1, t2 T2) func() R {
|
||||||
|
return func() R {
|
||||||
|
return f(t1, t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bind3[T1, T2, T3, R any](f func(T1, T2, T3) R, t1 T1, t2 T2, t3 T3) func() R {
|
||||||
|
return func() R {
|
||||||
|
return f(t1, t2, t3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SK[T1, T2 any](_ T1, t2 T2) T2 {
|
||||||
|
return t2
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ignore1[T, R any](f func() R) func(T) R {
|
||||||
|
return func(_ T) R {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ignore1st[T1, T2, R any](f func(T2) R) func(T1, T2) R {
|
||||||
|
return func(_ T1, t2 T2) R {
|
||||||
|
return f(t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ignore2nd[T1, T2, R any](f func(T1) R) func(T1, T2) R {
|
||||||
|
return func(t1 T1, _ T2) R {
|
||||||
|
return f(t1)
|
||||||
|
}
|
||||||
|
}
|
9
function/constants.go
Normal file
9
function/constants.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
var ConstTrue = Constant(true)
|
||||||
|
var ConstFalse = Constant(false)
|
||||||
|
|
||||||
|
// ConstNil returns nil
|
||||||
|
func ConstNil[A any]() *A {
|
||||||
|
return (*A)(nil)
|
||||||
|
}
|
77
function/curry.go
Normal file
77
function/curry.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
func Curry1[T1, R any](f func(T1) R) func(T1) R {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry2[T1, T2, R any](f func(T1, T2) R) func(T1) func(T2) R {
|
||||||
|
return func(t1 T1) func(T2) R {
|
||||||
|
return func(t2 T2) R {
|
||||||
|
return f(t1, t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry3[T1, T2, T3, R any](f func(T1, T2, T3) R) func(T1) func(T2) func(T3) R {
|
||||||
|
return func(t1 T1) func(T2) func(T3) R {
|
||||||
|
return func(t2 T2) func(T3) R {
|
||||||
|
return func(t3 T3) R {
|
||||||
|
return f(t1, t2, t3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) R) func(T1) func(T2) func(T3) func(T4) R {
|
||||||
|
return func(t1 T1) func(T2) func(T3) func(T4) R {
|
||||||
|
return func(t2 T2) func(T3) func(T4) R {
|
||||||
|
return func(t3 T3) func(T4) R {
|
||||||
|
return func(t4 T4) R {
|
||||||
|
return f(t1, t2, t3, t4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Curry5[T1, T2, T3, T4, T5, R any](f func(T1, T2, T3, T4, T5) R) func(T1) func(T2) func(T3) func(T4) func(T5) R {
|
||||||
|
return func(t1 T1) func(T2) func(T3) func(T4) func(T5) R {
|
||||||
|
return func(t2 T2) func(T3) func(T4) func(T5) R {
|
||||||
|
return func(t3 T3) func(T4) func(T5) R {
|
||||||
|
return func(t4 T4) func(T5) R {
|
||||||
|
return func(t5 T5) R {
|
||||||
|
return f(t1, t2, t3, t4, t5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry1[T1, R any](f func(T1) R) func(T1) R {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry2[T1, T2, R any](f func(T1) func(T2) R) func(T1, T2) R {
|
||||||
|
return func(t1 T1, t2 T2) R {
|
||||||
|
return f(t1)(t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry3[T1, T2, T3, R any](f func(T1) func(T2) func(T3) R) func(T1, T2, T3) R {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3) R {
|
||||||
|
return f(t1)(t2)(t3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry4[T1, T2, T3, T4, R any](f func(T1) func(T2) func(T3) func(T4) R) func(T1, T2, T3, T4) R {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4) R {
|
||||||
|
return f(t1)(t2)(t3)(t4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uncurry5[T1, T2, T3, T4, T5, R any](f func(T1) func(T2) func(T3) func(T4) func(T5) R) func(T1, T2, T3, T4, T5) R {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) R {
|
||||||
|
return f(t1)(t2)(t3)(t4)(t5)
|
||||||
|
}
|
||||||
|
}
|
218
function/function.go
Normal file
218
function/function.go
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
// what a mess, golang does not have proper types ...
|
||||||
|
|
||||||
|
func Pipe1[A, R any](a A, f1 func(a A) R) R {
|
||||||
|
// return f1(a)
|
||||||
|
r1 := f1(a)
|
||||||
|
return r1
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe2[A, T1, R any](a A, f1 func(a A) T1, f2 func(t1 T1) R) R {
|
||||||
|
// return f2(f1(a))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
return r2
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe3[A, T1, T2, R any](a A, f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) R) R {
|
||||||
|
// return f3(f2(f1(a)))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
r3 := f3(r2)
|
||||||
|
return r3
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe4[A, T1, T2, T3, R any](a A, f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) R) R {
|
||||||
|
// return f4(f3(f2(f1(a))))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
r3 := f3(r2)
|
||||||
|
r4 := f4(r3)
|
||||||
|
return r4
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe5[A, T1, T2, T3, T4, R any](a A, f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) R) R {
|
||||||
|
// return f5(f4(f3(f2(f1(a)))))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
r3 := f3(r2)
|
||||||
|
r4 := f4(r3)
|
||||||
|
r5 := f5(r4)
|
||||||
|
return r5
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe6[A, T1, T2, T3, T4, T5, R any](a A, f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) R) R {
|
||||||
|
// return f6(f5(f4(f3(f2(f1(a))))))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
r3 := f3(r2)
|
||||||
|
r4 := f4(r3)
|
||||||
|
r5 := f5(r4)
|
||||||
|
r6 := f6(r5)
|
||||||
|
return r6
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe7[A, T1, T2, T3, T4, T5, T6, R any](a A, f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) T6, f7 func(t6 T6) R) R {
|
||||||
|
// return f7(f6(f5(f4(f3(f2(f1(a)))))))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
r3 := f3(r2)
|
||||||
|
r4 := f4(r3)
|
||||||
|
r5 := f5(r4)
|
||||||
|
r6 := f6(r5)
|
||||||
|
r7 := f7(r6)
|
||||||
|
return r7
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe8[A, T1, T2, T3, T4, T5, T6, T7, R any](a A, f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) T6, f7 func(t6 T6) T7, f8 func(t7 T7) R) R {
|
||||||
|
// return f8(f7(f6(f5(f4(f3(f2(f1(a))))))))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
r3 := f3(r2)
|
||||||
|
r4 := f4(r3)
|
||||||
|
r5 := f5(r4)
|
||||||
|
r6 := f6(r5)
|
||||||
|
r7 := f7(r6)
|
||||||
|
r8 := f8(r7)
|
||||||
|
return r8
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pipe9[A, T1, T2, T3, T4, T5, T6, T7, T8, R any](a A, f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) T6, f7 func(t6 T6) T7, f8 func(t7 T7) T8, f9 func(t8 T8) R) R {
|
||||||
|
// return f9(f8(f7(f6(f5(f4(f3(f2(f1(a)))))))))
|
||||||
|
r1 := f1(a)
|
||||||
|
r2 := f2(r1)
|
||||||
|
r3 := f3(r2)
|
||||||
|
r4 := f4(r3)
|
||||||
|
r5 := f5(r4)
|
||||||
|
r6 := f6(r5)
|
||||||
|
r7 := f7(r6)
|
||||||
|
r8 := f8(r7)
|
||||||
|
r9 := f9(r8)
|
||||||
|
return r9
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow1[A, R any](f1 func(a A) R) func(a A) R {
|
||||||
|
return f1
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow2[A, T1, R any](f1 func(a A) T1, f2 func(t1 T1) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe2(a, f1, f2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow3[A, T1, T2, R any](f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe3(a, f1, f2, f3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow4[A, T1, T2, T3, R any](f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe4(a, f1, f2, f3, f4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow5[A, T1, T2, T3, T4, R any](f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe5(a, f1, f2, f3, f4, f5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow6[A, T1, T2, T3, T4, T5, R any](f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe6(a, f1, f2, f3, f4, f5, f6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow7[A, T1, T2, T3, T4, T5, T6, R any](f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) T6, f7 func(t6 T6) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe7(a, f1, f2, f3, f4, f5, f6, f7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow8[A, T1, T2, T3, T4, T5, T6, T7, R any](f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) T6, f7 func(t6 T6) T7, f8 func(t7 T7) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe8(a, f1, f2, f3, f4, f5, f6, f7, f8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flow9[A, T1, T2, T3, T4, T5, T6, T7, T8, R any](f1 func(a A) T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) T4, f5 func(t4 T4) T5, f6 func(t5 T5) T6, f7 func(t6 T6) T7, f8 func(t7 T7) T8, f9 func(t8 T8) R) func(a A) R {
|
||||||
|
return func(a A) R {
|
||||||
|
return Pipe9(a, f1, f2, f3, f4, f5, f6, f7, f8, f9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identity returns the value 'a'
|
||||||
|
func Identity[A any](a A) A {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant creates a nullary function that returns the constant value 'a'
|
||||||
|
func Constant[A any](a A) func() A {
|
||||||
|
return func() A {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant1 creates a unary function that returns the constant value 'a' and ignores its input
|
||||||
|
func Constant1[B, A any](a A) func(B) A {
|
||||||
|
return func(_ B) A {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant2 creates a binary function that returns the constant value 'a' and ignores its inputs
|
||||||
|
func Constant2[B, C, A any](a A) func(B, C) A {
|
||||||
|
return func(_ B, _ C) A {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNil[A any](a *A) bool {
|
||||||
|
return a == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNonNil[A any](a *A) bool {
|
||||||
|
return a != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap returns a new binary function that changes the order of input parameters
|
||||||
|
func Swap[T1, T2, R any](f func(T1, T2) R) func(T2, T1) R {
|
||||||
|
return func(t2 T2, t1 T1) R {
|
||||||
|
return f(t1, t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First returns the first out of two input values
|
||||||
|
func First[T1, T2 any](t1 T1, _ T2) T1 {
|
||||||
|
return t1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second returns the second out of two input values
|
||||||
|
func Second[T1, T2 any](_ T1, t2 T2) T2 {
|
||||||
|
return t2
|
||||||
|
}
|
||||||
|
|
||||||
|
func Nullary1[R any](f1 func() R) func() R {
|
||||||
|
return f1
|
||||||
|
}
|
||||||
|
|
||||||
|
func Nullary2[T1, R any](f1 func() T1, f2 func(t1 T1) R) func() R {
|
||||||
|
return func() R {
|
||||||
|
return Pipe1(f1(), f2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Nullary3[T1, T2, R any](f1 func() T1, f2 func(t1 T1) T2, f3 func(t2 T2) R) func() R {
|
||||||
|
return func() R {
|
||||||
|
return Pipe2(f1(), f2, f3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Nullary4[T1, T2, T3, R any](f1 func() T1, f2 func(t1 T1) T2, f3 func(t2 T2) T3, f4 func(t3 T3) R) func() R {
|
||||||
|
return func() R {
|
||||||
|
return Pipe3(f1(), f2, f3, f4)
|
||||||
|
}
|
||||||
|
}
|
9
function/ref.go
Normal file
9
function/ref.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
func Ref[A any](a A) *A {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func Deref[A any](a *A) A {
|
||||||
|
return *a
|
||||||
|
}
|
10
function/ternary.go
Normal file
10
function/ternary.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
func Ternary[A, B any](pred func(A) bool, onTrue func(A) B, onFalse func(A) B) func(A) B {
|
||||||
|
return func(a A) B {
|
||||||
|
if pred(a) {
|
||||||
|
return onTrue(a)
|
||||||
|
}
|
||||||
|
return onFalse(a)
|
||||||
|
}
|
||||||
|
}
|
5
function/type.go
Normal file
5
function/type.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
func ToAny[A any](a A) any {
|
||||||
|
return any(a)
|
||||||
|
}
|
61
function/variadic.go
Normal file
61
function/variadic.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
func Variadic0[V, R any](f func([]V) R) func(...V) R {
|
||||||
|
return func(v ...V) R {
|
||||||
|
return f(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic1[T1, V, R any](f func(T1, []V) R) func(T1, ...V) R {
|
||||||
|
return func(t1 T1, v ...V) R {
|
||||||
|
return f(t1, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic2[T1, T2, V, R any](f func(T1, T2, []V) R) func(T1, T2, ...V) R {
|
||||||
|
return func(t1 T1, t2 T2, v ...V) R {
|
||||||
|
return f(t1, t2, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, []V) R) func(T1, T2, T3, ...V) R {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, v ...V) R {
|
||||||
|
return f(t1, t2, t3, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Variadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, []V) R) func(T1, T2, T3, T4, ...V) R {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4, v ...V) R {
|
||||||
|
return f(t1, t2, t3, t4, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic0[V, R any](f func(...V) R) func([]V) R {
|
||||||
|
return func(v []V) R {
|
||||||
|
return f(v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic1[T1, V, R any](f func(T1, ...V) R) func(T1, []V) R {
|
||||||
|
return func(t1 T1, v []V) R {
|
||||||
|
return f(t1, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic2[T1, T2, V, R any](f func(T1, T2, ...V) R) func(T1, T2, []V) R {
|
||||||
|
return func(t1 T1, t2 T2, v []V) R {
|
||||||
|
return f(t1, t2, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, ...V) R) func(T1, T2, T3, []V) R {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, v []V) R {
|
||||||
|
return f(t1, t2, t3, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unvariadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, ...V) R) func(T1, T2, T3, T4, []V) R {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4, v []V) R {
|
||||||
|
return f(t1, t2, t3, t4, v...)
|
||||||
|
}
|
||||||
|
}
|
12
go.mod
Normal file
12
go.mod
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module github.com/ibm/fp-go
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.8.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
17
go.sum
Normal file
17
go.sum
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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/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=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
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=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
126
internal/applicative/testing/law.go
Normal file
126
internal/applicative/testing/law.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
E "github.com/ibm/fp-go/eq"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
L "github.com/ibm/fp-go/internal/apply/testing"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Applicative identity law
|
||||||
|
//
|
||||||
|
// A.ap(A.of(a => a), fa) <-> fa
|
||||||
|
func AssertIdentity[HKTA, HKTAA, A any](t *testing.T,
|
||||||
|
eq E.Eq[HKTA],
|
||||||
|
|
||||||
|
fof func(func(A) A) HKTAA,
|
||||||
|
|
||||||
|
fap func(HKTAA, HKTA) HKTA,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
|
||||||
|
left := fap(fof(F.Identity[A]), fa)
|
||||||
|
right := fa
|
||||||
|
|
||||||
|
return assert.True(t, eq.Equals(left, right), "Applicative identity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applicative homomorphism law
|
||||||
|
//
|
||||||
|
// A.ap(A.of(ab), A.of(a)) <-> A.of(ab(a))
|
||||||
|
func AssertHomomorphism[HKTA, HKTB, HKTAB, A, B any](t *testing.T,
|
||||||
|
eq E.Eq[HKTB],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
fofb func(B) HKTB,
|
||||||
|
fofab func(func(A) B) HKTAB,
|
||||||
|
|
||||||
|
fap func(HKTAB, HKTA) HKTB,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
) func(a A) bool {
|
||||||
|
return func(a A) bool {
|
||||||
|
|
||||||
|
left := fap(fofab(ab), fofa(a))
|
||||||
|
right := fofb(ab(a))
|
||||||
|
|
||||||
|
return assert.True(t, eq.Equals(left, right), "Applicative homomorphism")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applicative interchange law
|
||||||
|
//
|
||||||
|
// A.ap(fab, A.of(a)) <-> A.ap(A.of(ab => ab(a)), fab)
|
||||||
|
func AssertInterchange[HKTA, HKTB, HKTAB, HKTABB, A, B any](t *testing.T,
|
||||||
|
eq E.Eq[HKTB],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
fofb func(B) HKTB,
|
||||||
|
fofab func(func(A) B) HKTAB,
|
||||||
|
fofabb func(func(func(A) B) B) HKTABB,
|
||||||
|
|
||||||
|
fapab func(HKTAB, HKTA) HKTB,
|
||||||
|
fapabb func(HKTABB, HKTAB) HKTB,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
) func(a A) bool {
|
||||||
|
return func(a A) bool {
|
||||||
|
|
||||||
|
fab := fofab(ab)
|
||||||
|
|
||||||
|
left := fapab(fab, fofa(a))
|
||||||
|
right := fapabb(fofabb(func(ab func(A) B) B {
|
||||||
|
return ab(a)
|
||||||
|
}), fab)
|
||||||
|
|
||||||
|
return assert.True(t, eq.Equals(left, right), "Applicative homomorphism")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange'
|
||||||
|
func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T,
|
||||||
|
eqa E.Eq[HKTA],
|
||||||
|
eqb E.Eq[HKTB],
|
||||||
|
eqc E.Eq[HKTC],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
fofb func(B) HKTB,
|
||||||
|
|
||||||
|
fofaa func(func(A) A) HKTAA,
|
||||||
|
fofab func(func(A) B) HKTAB,
|
||||||
|
fofbc func(func(B) C) HKTBC,
|
||||||
|
fofabb func(func(func(A) B) B) HKTABB,
|
||||||
|
|
||||||
|
faa func(HKTA, func(A) A) HKTA,
|
||||||
|
fab func(HKTA, func(A) B) HKTB,
|
||||||
|
fac func(HKTA, func(A) C) HKTC,
|
||||||
|
fbc func(HKTB, func(B) C) HKTC,
|
||||||
|
|
||||||
|
fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC,
|
||||||
|
|
||||||
|
fapaa func(HKTAA, HKTA) HKTA,
|
||||||
|
fapab func(HKTAB, HKTA) HKTB,
|
||||||
|
fapbc func(HKTBC, HKTB) HKTC,
|
||||||
|
fapac func(HKTAC, HKTA) HKTC,
|
||||||
|
|
||||||
|
fapabb func(HKTABB, HKTAB) HKTB,
|
||||||
|
fapabac func(HKTABAC, HKTAB) HKTAC,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(a A) bool {
|
||||||
|
// apply laws
|
||||||
|
apply := L.AssertLaws(t, eqa, eqc, fofab, fofbc, faa, fab, fac, fbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc)
|
||||||
|
// applicative laws
|
||||||
|
identity := AssertIdentity(t, eqa, fofaa, fapaa)
|
||||||
|
homomorphism := AssertHomomorphism(t, eqb, fofa, fofb, fofab, fapab, ab)
|
||||||
|
interchange := AssertInterchange(t, eqb, fofa, fofb, fofab, fofabb, fapab, fapabb, ab)
|
||||||
|
|
||||||
|
return func(a A) bool {
|
||||||
|
fa := fofa(a)
|
||||||
|
return apply(fa) && identity(fa) && homomorphism(a) && interchange(a)
|
||||||
|
}
|
||||||
|
}
|
94
internal/apply/ap.go
Normal file
94
internal/apply/ap.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package Apply
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HKTFGA = HKT<F, HKT<G, A>>
|
||||||
|
// HKTFGB = HKT<F, HKT<G, B>>
|
||||||
|
// HKTFGAB = HKT<F, HKT<G, (a: A) => B>>
|
||||||
|
|
||||||
|
// HKTGA = HKT<G, A>
|
||||||
|
// HKTGB = HKT<G, B>
|
||||||
|
// HKTGAB = HKT<G, (a: A) => B>
|
||||||
|
func MonadAp[HKTGA, HKTGB, HKTGAB, HKTFGAB, HKTFGGAB, HKTFGA, HKTFGB any](
|
||||||
|
fap func(HKTFGGAB, HKTFGA) HKTFGB,
|
||||||
|
fmap func(HKTFGAB, func(HKTGAB) func(HKTGA) HKTGB) HKTFGGAB,
|
||||||
|
gap func(HKTGAB, HKTGA) HKTGB,
|
||||||
|
|
||||||
|
fab HKTFGAB,
|
||||||
|
fa HKTFGA) HKTFGB {
|
||||||
|
|
||||||
|
return fap(fmap(fab, F.Bind1st(F.Bind1st[HKTGAB, HKTGA, HKTGB], gap)), fa)
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function ap<F, G>(
|
||||||
|
// F: Apply<F>,
|
||||||
|
// G: Apply<G>
|
||||||
|
// ): <A>(fa: HKT<F, HKT<G, A>>) => <B>(fab: HKT<F, HKT<G, (a: A) => B>>) => HKT<F, HKT<G, B>> {
|
||||||
|
// return <A>(fa: HKT<F, HKT<G, A>>) => <B>(fab: HKT<F, HKT<G, (a: A) => B>>): HKT<F, HKT<G, B>> =>
|
||||||
|
// F.ap(
|
||||||
|
// F.map(fab, (gab) => (ga: HKT<G, A>) => G.ap(gab, ga)),
|
||||||
|
// fa
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function apFirst<F>(A: Apply<F>): <B>(second: HKT<F, B>) => <A>(first: HKT<F, A>) => HKT<F, A> {
|
||||||
|
// return (second) => (first) =>
|
||||||
|
// A.ap(
|
||||||
|
// A.map(first, (a) => () => a),
|
||||||
|
// second
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Functor<F>.map: <A, () => A>(fa: HKT<F, A>, f: (a: A) => () => A) => HKT<F, () => A>
|
||||||
|
|
||||||
|
// Apply<F>.ap: <B, A>(fab: HKT<F, (a: B) => A>, fa: HKT<F, B>) => HKT<F, A>
|
||||||
|
|
||||||
|
func MonadApFirst[HKTGA, HKTGB, HKTGBA, A, B any](
|
||||||
|
fap func(HKTGBA, HKTGB) HKTGA,
|
||||||
|
fmap func(HKTGA, func(A) func(B) A) HKTGBA,
|
||||||
|
|
||||||
|
first HKTGA,
|
||||||
|
second HKTGB,
|
||||||
|
) HKTGA {
|
||||||
|
return fap(
|
||||||
|
fmap(first, F.Constant1[B, A]),
|
||||||
|
second,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApFirst[HKTGA, HKTGB, HKTGBA, A, B any](
|
||||||
|
fap func(HKTGBA, HKTGB) HKTGA,
|
||||||
|
fmap func(HKTGA, func(A) func(B) A) HKTGBA,
|
||||||
|
|
||||||
|
second HKTGB,
|
||||||
|
) func(HKTGA) HKTGA {
|
||||||
|
return func(first HKTGA) HKTGA {
|
||||||
|
return MonadApFirst(fap, fmap, first, second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadApSecond[HKTGA, HKTGB, HKTGBB, A, B any](
|
||||||
|
fap func(HKTGBB, HKTGB) HKTGB,
|
||||||
|
fmap func(HKTGA, func(A) func(B) B) HKTGBB,
|
||||||
|
|
||||||
|
first HKTGA,
|
||||||
|
second HKTGB,
|
||||||
|
) HKTGB {
|
||||||
|
return fap(
|
||||||
|
fmap(first, F.Constant1[A](F.Identity[B])),
|
||||||
|
second,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApSecond[HKTGA, HKTGB, HKTGBB, A, B any](
|
||||||
|
fap func(HKTGBB, HKTGB) HKTGB,
|
||||||
|
fmap func(HKTGA, func(A) func(B) B) HKTGBB,
|
||||||
|
|
||||||
|
second HKTGB,
|
||||||
|
) func(HKTGA) HKTGB {
|
||||||
|
return func(first HKTGA) HKTGB {
|
||||||
|
return MonadApSecond(fap, fmap, first, second)
|
||||||
|
}
|
||||||
|
}
|
82
internal/apply/testing/laws.go
Normal file
82
internal/apply/testing/laws.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
E "github.com/ibm/fp-go/eq"
|
||||||
|
FCT "github.com/ibm/fp-go/internal/functor/testing"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apply associative composition law
|
||||||
|
//
|
||||||
|
// F.ap(F.ap(F.map(fbc, bc => ab => a => bc(ab(a))), fab), fa) <-> F.ap(fbc, F.ap(fab, fa))
|
||||||
|
func AssertAssociativeComposition[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T,
|
||||||
|
eq E.Eq[HKTC],
|
||||||
|
|
||||||
|
fofab func(func(A) B) HKTAB,
|
||||||
|
fofbc func(func(B) C) HKTBC,
|
||||||
|
|
||||||
|
fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC,
|
||||||
|
|
||||||
|
fapab func(HKTAB, HKTA) HKTB,
|
||||||
|
fapbc func(HKTBC, HKTB) HKTC,
|
||||||
|
fapac func(HKTAC, HKTA) HKTC,
|
||||||
|
|
||||||
|
fapabac func(HKTABAC, HKTAB) HKTAC,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
|
||||||
|
fab := fofab(ab)
|
||||||
|
fbc := fofbc(bc)
|
||||||
|
|
||||||
|
left := fapac(fapabac(fmap(fbc, func(bc func(B) C) func(func(A) B) func(A) C {
|
||||||
|
return func(ab func(A) B) func(A) C {
|
||||||
|
return func(a A) C {
|
||||||
|
return bc(ab(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), fab), fa)
|
||||||
|
|
||||||
|
right := fapbc(fbc, fapab(fab, fa))
|
||||||
|
|
||||||
|
return assert.True(t, eq.Equals(left, right), "Apply associative composition")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertLaws asserts the apply laws `identity`, `composition` and `associative composition`
|
||||||
|
func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T,
|
||||||
|
eqa E.Eq[HKTA],
|
||||||
|
eqc E.Eq[HKTC],
|
||||||
|
|
||||||
|
fofab func(func(A) B) HKTAB,
|
||||||
|
fofbc func(func(B) C) HKTBC,
|
||||||
|
|
||||||
|
faa func(HKTA, func(A) A) HKTA,
|
||||||
|
fab func(HKTA, func(A) B) HKTB,
|
||||||
|
fac func(HKTA, func(A) C) HKTC,
|
||||||
|
fbc func(HKTB, func(B) C) HKTC,
|
||||||
|
|
||||||
|
fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC,
|
||||||
|
|
||||||
|
fapab func(HKTAB, HKTA) HKTB,
|
||||||
|
fapbc func(HKTBC, HKTB) HKTC,
|
||||||
|
fapac func(HKTAC, HKTA) HKTC,
|
||||||
|
|
||||||
|
fapabac func(HKTABAC, HKTAB) HKTAC,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
// functor laws
|
||||||
|
functor := FCT.AssertLaws(t, eqa, eqc, faa, fab, fac, fbc, ab, bc)
|
||||||
|
// associative composition laws
|
||||||
|
composition := AssertAssociativeComposition(t, eqc, fofab, fofbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc)
|
||||||
|
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
return functor(fa) && composition(fa)
|
||||||
|
}
|
||||||
|
}
|
61
internal/array/array.go
Normal file
61
internal/array/array.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
func Slice[GA ~[]A, A any](low, high int) func(as GA) GA {
|
||||||
|
return func(as GA) GA {
|
||||||
|
return as[low:high]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsEmpty[GA ~[]A, A any](as GA) bool {
|
||||||
|
return len(as) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNil[GA ~[]A, A any](as GA) bool {
|
||||||
|
return as == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNonNil[GA ~[]A, A any](as GA) bool {
|
||||||
|
return as != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
|
||||||
|
current := initial
|
||||||
|
count := len(fa)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
current = f(current, fa[i])
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func Append[GA ~[]A, A any](as GA, a A) GA {
|
||||||
|
return append(as, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Empty[GA ~[]A, A any]() GA {
|
||||||
|
return make(GA, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func upsertAt[GA ~[]A, A any](fa GA, a A) GA {
|
||||||
|
buf := make(GA, len(fa)+1)
|
||||||
|
buf[copy(buf, fa)] = a
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpsertAt[GA ~[]A, A any](a A) func(GA) GA {
|
||||||
|
return func(ma GA) GA {
|
||||||
|
return upsertAt(ma, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB {
|
||||||
|
count := len(as)
|
||||||
|
bs := make(GB, count)
|
||||||
|
for i := count - 1; i >= 0; i-- {
|
||||||
|
bs[i] = f(as[i])
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConstNil[GA ~[]A, A any]() GA {
|
||||||
|
return (GA)(nil)
|
||||||
|
}
|
70
internal/array/traverse.go
Normal file
70
internal/array/traverse.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package array
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||||
|
|
||||||
|
HKTRB = HKT<GB>
|
||||||
|
HKTB = HKT<B>
|
||||||
|
HKTAB = HKT<func(A)B>
|
||||||
|
*/
|
||||||
|
func MonadTraverse[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(GB) HKTRB,
|
||||||
|
_map func(HKTRB, func(GB) func(B) GB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
ta GA,
|
||||||
|
f func(A) HKTB) HKTRB {
|
||||||
|
return MonadTraverseReduce(_of, _map, _ap, ta, f, Append[GB, B], Empty[GB]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Traverse[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(GB) HKTRB,
|
||||||
|
_map func(HKTRB, func(GB) func(B) GB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
f func(A) HKTB) func(GA) HKTRB {
|
||||||
|
|
||||||
|
return func(ma GA) HKTRB {
|
||||||
|
return MonadTraverse(_of, _map, _ap, ma, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadTraverseReduce[GA ~[]A, GB, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(GB) HKTRB,
|
||||||
|
_map func(HKTRB, func(GB) func(B) GB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
ta GA,
|
||||||
|
|
||||||
|
transform func(A) HKTB,
|
||||||
|
reduce func(GB, B) GB,
|
||||||
|
initial GB,
|
||||||
|
) HKTRB {
|
||||||
|
mmap := F.Bind2nd(_map, F.Curry2(reduce))
|
||||||
|
|
||||||
|
return Reduce(ta, func(r HKTRB, a A) HKTRB {
|
||||||
|
return _ap(
|
||||||
|
mmap(r),
|
||||||
|
transform(a),
|
||||||
|
)
|
||||||
|
}, _of(initial))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TraverseReduce[GA ~[]A, GB, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(GB) HKTRB,
|
||||||
|
_map func(HKTRB, func(GB) func(B) GB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
transform func(A) HKTB,
|
||||||
|
reduce func(GB, B) GB,
|
||||||
|
initial GB,
|
||||||
|
) func(GA) HKTRB {
|
||||||
|
return func(ta GA) HKTRB {
|
||||||
|
return MonadTraverseReduce(_of, _map, _ap, ta, transform, reduce, initial)
|
||||||
|
}
|
||||||
|
}
|
46
internal/chain/chain.go
Normal file
46
internal/chain/chain.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HKTA=HKT[A]
|
||||||
|
// HKTB=HKT[B]
|
||||||
|
func MonadChainFirst[A, B, HKTA, HKTB any](
|
||||||
|
mchain func(HKTA, func(A) HKTA) HKTA,
|
||||||
|
mmap func(HKTB, func(B) A) HKTA,
|
||||||
|
first HKTA,
|
||||||
|
f func(A) HKTB,
|
||||||
|
) HKTA {
|
||||||
|
return mchain(first, func(a A) HKTA {
|
||||||
|
return mmap(f(a), F.Constant1[B](a))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChain[A, B, HKTA, HKTB any](
|
||||||
|
mchain func(HKTA, func(A) HKTB) HKTB,
|
||||||
|
first HKTA,
|
||||||
|
f func(A) HKTB,
|
||||||
|
) HKTB {
|
||||||
|
return mchain(first, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKTA=HKT[A]
|
||||||
|
// HKTB=HKT[B]
|
||||||
|
func ChainFirst[A, B, HKTA, HKTB any](
|
||||||
|
mchain func(HKTA, func(A) HKTA) HKTA,
|
||||||
|
mmap func(HKTB, func(B) A) HKTA,
|
||||||
|
f func(A) HKTB) func(HKTA) HKTA {
|
||||||
|
return F.Bind2nd(mchain, func(a A) HKTA {
|
||||||
|
return mmap(f(a), F.Constant1[B](a))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Chain[A, B, HKTA, HKTB any](
|
||||||
|
mchain func(HKTA, func(A) HKTB) HKTB,
|
||||||
|
f func(A) HKTB,
|
||||||
|
) func(HKTA) HKTB {
|
||||||
|
return func(first HKTA) HKTB {
|
||||||
|
return MonadChain[A, B](mchain, first, f)
|
||||||
|
}
|
||||||
|
}
|
84
internal/chain/testing/laws.go
Normal file
84
internal/chain/testing/laws.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
E "github.com/ibm/fp-go/eq"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
L "github.com/ibm/fp-go/internal/apply/testing"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Chain associativity law
|
||||||
|
//
|
||||||
|
// F.chain(F.chain(fa, afb), bfc) <-> F.chain(fa, a => F.chain(afb(a), bfc))
|
||||||
|
func AssertAssociativity[HKTA, HKTB, HKTC, A, B, C any](t *testing.T,
|
||||||
|
eq E.Eq[HKTC],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
fofb func(B) HKTB,
|
||||||
|
fofc func(C) HKTC,
|
||||||
|
|
||||||
|
chainab func(HKTA, func(A) HKTB) HKTB,
|
||||||
|
chainac func(HKTA, func(A) HKTC) HKTC,
|
||||||
|
chainbc func(HKTB, func(B) HKTC) HKTC,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
|
||||||
|
afb := F.Flow2(ab, fofb)
|
||||||
|
bfc := F.Flow2(bc, fofc)
|
||||||
|
|
||||||
|
left := chainbc(chainab(fa, afb), bfc)
|
||||||
|
|
||||||
|
right := chainac(fa, func(a A) HKTC {
|
||||||
|
return chainbc(afb(a), bfc)
|
||||||
|
})
|
||||||
|
|
||||||
|
return assert.True(t, eq.Equals(left, right), "Chain associativity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertLaws asserts the apply laws `identity`, `composition`, `associative composition` and `associativity`
|
||||||
|
func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T,
|
||||||
|
eqa E.Eq[HKTA],
|
||||||
|
eqc E.Eq[HKTC],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
fofb func(B) HKTB,
|
||||||
|
fofc func(C) HKTC,
|
||||||
|
|
||||||
|
fofab func(func(A) B) HKTAB,
|
||||||
|
fofbc func(func(B) C) HKTBC,
|
||||||
|
|
||||||
|
faa func(HKTA, func(A) A) HKTA,
|
||||||
|
fab func(HKTA, func(A) B) HKTB,
|
||||||
|
fac func(HKTA, func(A) C) HKTC,
|
||||||
|
fbc func(HKTB, func(B) C) HKTC,
|
||||||
|
|
||||||
|
fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC,
|
||||||
|
|
||||||
|
chainab func(HKTA, func(A) HKTB) HKTB,
|
||||||
|
chainac func(HKTA, func(A) HKTC) HKTC,
|
||||||
|
chainbc func(HKTB, func(B) HKTC) HKTC,
|
||||||
|
|
||||||
|
fapab func(HKTAB, HKTA) HKTB,
|
||||||
|
fapbc func(HKTBC, HKTB) HKTC,
|
||||||
|
fapac func(HKTAC, HKTA) HKTC,
|
||||||
|
|
||||||
|
fapabac func(HKTABAC, HKTAB) HKTAC,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
// apply laws
|
||||||
|
apply := L.AssertLaws(t, eqa, eqc, fofab, fofbc, faa, fab, fac, fbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc)
|
||||||
|
// chain laws
|
||||||
|
associativity := AssertAssociativity(t, eqc, fofa, fofb, fofc, chainab, chainac, chainbc, ab, bc)
|
||||||
|
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
return apply(fa) && associativity(fa)
|
||||||
|
}
|
||||||
|
}
|
31
internal/exec/exec.go
Normal file
31
internal/exec/exec.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package exec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
EX "github.com/ibm/fp-go/exec"
|
||||||
|
|
||||||
|
T "github.com/ibm/fp-go/tuple"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Exec(ctx context.Context, name string, args []string, in []byte) (EX.CommandOutput, error) {
|
||||||
|
// command input
|
||||||
|
cmd := exec.CommandContext(ctx, name, args...)
|
||||||
|
cmd.Stdin = bytes.NewReader(in)
|
||||||
|
// command result
|
||||||
|
var stdOut bytes.Buffer
|
||||||
|
var stdErr bytes.Buffer
|
||||||
|
|
||||||
|
cmd.Stdout = &stdOut
|
||||||
|
cmd.Stderr = &stdErr
|
||||||
|
// execute the command
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("command execution of [%s][%s] failed, stdout [%s], stderr [%s], cause [%w]", name, args, stdOut.String(), stdErr.String(), err)
|
||||||
|
}
|
||||||
|
// return the outputs
|
||||||
|
return T.MakeTuple2(stdOut.Bytes(), stdErr.Bytes()), err
|
||||||
|
}
|
57
internal/functor/testing/laws.go
Normal file
57
internal/functor/testing/laws.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
E "github.com/ibm/fp-go/eq"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Functor identity law
|
||||||
|
//
|
||||||
|
// F.map(fa, a => a) <-> fa
|
||||||
|
func AssertIdentity[HKTA, A any](t *testing.T, eq E.Eq[HKTA], fmap func(HKTA, func(A) A) HKTA) func(fa HKTA) bool {
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
return assert.True(t, eq.Equals(fa, fmap(fa, F.Identity[A])), "Functor identity law")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functor composition law
|
||||||
|
//
|
||||||
|
// F.map(fa, a => bc(ab(a))) <-> F.map(F.map(fa, ab), bc)
|
||||||
|
func AssertComposition[HKTA, HKTB, HKTC, A, B, C any](
|
||||||
|
t *testing.T,
|
||||||
|
|
||||||
|
eq E.Eq[HKTC],
|
||||||
|
|
||||||
|
fab func(HKTA, func(A) B) HKTB,
|
||||||
|
fac func(HKTA, func(A) C) HKTC,
|
||||||
|
fbc func(HKTB, func(B) C) HKTC,
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
return assert.True(t, eq.Equals(fac(fa, F.Flow2(ab, bc)), fbc(fab(fa, ab), bc)), "Functor composition law")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertLaws asserts the functor laws `identity` and `composition`
|
||||||
|
func AssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T,
|
||||||
|
eqa E.Eq[HKTA],
|
||||||
|
eqc E.Eq[HKTC],
|
||||||
|
|
||||||
|
faa func(HKTA, func(A) A) HKTA,
|
||||||
|
fab func(HKTA, func(A) B) HKTB,
|
||||||
|
fac func(HKTA, func(A) C) HKTC,
|
||||||
|
fbc func(HKTB, func(B) C) HKTC,
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
identity := AssertIdentity(t, eqa, faa)
|
||||||
|
composition := AssertComposition(t, eqc, fab, fac, fbc, ab, bc)
|
||||||
|
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
return identity(fa) && composition(fa)
|
||||||
|
}
|
||||||
|
}
|
107
internal/monad/testing/laws.go
Normal file
107
internal/monad/testing/laws.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
E "github.com/ibm/fp-go/eq"
|
||||||
|
LA "github.com/ibm/fp-go/internal/applicative/testing"
|
||||||
|
LC "github.com/ibm/fp-go/internal/chain/testing"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apply monad left identity law
|
||||||
|
//
|
||||||
|
// M.chain(M.of(a), f) <-> f(a)
|
||||||
|
func AssertLeftIdentity[HKTA, HKTB, A, B any](t *testing.T,
|
||||||
|
eq E.Eq[HKTB],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
fofb func(B) HKTB,
|
||||||
|
|
||||||
|
fchain func(HKTA, func(A) HKTB) HKTB,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
) func(a A) bool {
|
||||||
|
return func(a A) bool {
|
||||||
|
|
||||||
|
f := func(a A) HKTB {
|
||||||
|
return fofb(ab(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
left := fchain(fofa(a), f)
|
||||||
|
right := f(a)
|
||||||
|
|
||||||
|
return assert.True(t, eq.Equals(left, right), "Monad left identity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply monad right identity law
|
||||||
|
//
|
||||||
|
// M.chain(fa, M.of) <-> fa
|
||||||
|
func AssertRightIdentity[HKTA, A any](t *testing.T,
|
||||||
|
eq E.Eq[HKTA],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
|
||||||
|
fchain func(HKTA, func(A) HKTA) HKTA,
|
||||||
|
) func(fa HKTA) bool {
|
||||||
|
return func(fa HKTA) bool {
|
||||||
|
|
||||||
|
left := fchain(fa, fofa)
|
||||||
|
right := fa
|
||||||
|
|
||||||
|
return assert.True(t, eq.Equals(left, right), "Monad right identity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange', `associativity`, `left identity`, `right identity`
|
||||||
|
func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T,
|
||||||
|
eqa E.Eq[HKTA],
|
||||||
|
eqb E.Eq[HKTB],
|
||||||
|
eqc E.Eq[HKTC],
|
||||||
|
|
||||||
|
fofa func(A) HKTA,
|
||||||
|
fofb func(B) HKTB,
|
||||||
|
fofc func(C) HKTC,
|
||||||
|
|
||||||
|
fofaa func(func(A) A) HKTAA,
|
||||||
|
fofab func(func(A) B) HKTAB,
|
||||||
|
fofbc func(func(B) C) HKTBC,
|
||||||
|
fofabb func(func(func(A) B) B) HKTABB,
|
||||||
|
|
||||||
|
faa func(HKTA, func(A) A) HKTA,
|
||||||
|
fab func(HKTA, func(A) B) HKTB,
|
||||||
|
fac func(HKTA, func(A) C) HKTC,
|
||||||
|
fbc func(HKTB, func(B) C) HKTC,
|
||||||
|
|
||||||
|
fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC,
|
||||||
|
|
||||||
|
chainaa func(HKTA, func(A) HKTA) HKTA,
|
||||||
|
chainab func(HKTA, func(A) HKTB) HKTB,
|
||||||
|
chainac func(HKTA, func(A) HKTC) HKTC,
|
||||||
|
chainbc func(HKTB, func(B) HKTC) HKTC,
|
||||||
|
|
||||||
|
fapaa func(HKTAA, HKTA) HKTA,
|
||||||
|
fapab func(HKTAB, HKTA) HKTB,
|
||||||
|
fapbc func(HKTBC, HKTB) HKTC,
|
||||||
|
fapac func(HKTAC, HKTA) HKTC,
|
||||||
|
|
||||||
|
fapabb func(HKTABB, HKTAB) HKTB,
|
||||||
|
fapabac func(HKTABAC, HKTAB) HKTAC,
|
||||||
|
|
||||||
|
ab func(A) B,
|
||||||
|
bc func(B) C,
|
||||||
|
) func(a A) bool {
|
||||||
|
// applicative laws
|
||||||
|
applicative := LA.AssertLaws(t, eqa, eqb, eqc, fofa, fofb, fofaa, fofab, fofbc, fofabb, faa, fab, fac, fbc, fmap, fapaa, fapab, fapbc, fapac, fapabb, fapabac, ab, bc)
|
||||||
|
// chain laws
|
||||||
|
chain := LC.AssertLaws(t, eqa, eqc, fofa, fofb, fofc, fofab, fofbc, faa, fab, fac, fbc, fmap, chainab, chainac, chainbc, fapab, fapbc, fapac, fapabac, ab, bc)
|
||||||
|
// monad laws
|
||||||
|
leftIdentity := AssertLeftIdentity(t, eqb, fofa, fofb, chainab, ab)
|
||||||
|
rightIdentity := AssertRightIdentity(t, eqa, fofa, chainaa)
|
||||||
|
|
||||||
|
return func(a A) bool {
|
||||||
|
fa := fofa(a)
|
||||||
|
return applicative(a) && chain(fa) && leftIdentity(a) && rightIdentity(fa)
|
||||||
|
}
|
||||||
|
}
|
33
internal/record/record.go
Normal file
33
internal/record/record.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package record
|
||||||
|
|
||||||
|
func Reduce[M ~map[K]V, K comparable, V, R any](r M, f func(R, V) R, initial R) R {
|
||||||
|
current := initial
|
||||||
|
for _, v := range r {
|
||||||
|
current = f(current, v)
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReduceWithIndex[M ~map[K]V, K comparable, V, R any](r M, f func(K, R, V) R, initial R) R {
|
||||||
|
current := initial
|
||||||
|
for k, v := range r {
|
||||||
|
current = f(k, current, v)
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReduceRef[M ~map[K]V, K comparable, V, R any](r M, f func(R, *V) R, initial R) R {
|
||||||
|
current := initial
|
||||||
|
for _, v := range r {
|
||||||
|
current = f(current, &v) // #nosec G601
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReduceRefWithIndex[M ~map[K]V, K comparable, V, R any](r M, f func(K, R, *V) R, initial R) R {
|
||||||
|
current := initial
|
||||||
|
for k, v := range r {
|
||||||
|
current = f(k, current, &v) // #nosec G601
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
93
internal/record/traverse.go
Normal file
93
internal/record/traverse.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package record
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createEmpty creates a new empty, read-write map
|
||||||
|
// this is different to Empty which creates a new read-only empty map
|
||||||
|
func createEmpty[N ~map[K]A, K comparable, A any]() N {
|
||||||
|
return make(N)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inserts the key/value pair into a read-write map for performance
|
||||||
|
// order of parameters is adjusted to be curryable
|
||||||
|
func addKey[N ~map[K]A, K comparable, A any](key K, m N, value A) N {
|
||||||
|
m[key] = value
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||||
|
|
||||||
|
HKTRB = HKT<MB>
|
||||||
|
HKTA = HKT<A>
|
||||||
|
HKTB = HKT<B>
|
||||||
|
HKTAB = HKT<func(A)B>
|
||||||
|
*/
|
||||||
|
func traverseWithIndex[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(MB) HKTRB,
|
||||||
|
_map func(HKTRB, func(MB) func(B) MB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
ta MA, f func(K, A) HKTB) HKTRB {
|
||||||
|
// this function inserts a value into a map with a given key
|
||||||
|
cb := F.Curry3(addKey[MB, K, B])
|
||||||
|
|
||||||
|
return ReduceWithIndex(ta, func(k K, r HKTRB, a A) HKTRB {
|
||||||
|
return _ap(
|
||||||
|
_map(r, cb(k)),
|
||||||
|
f(k, a),
|
||||||
|
)
|
||||||
|
}, _of(createEmpty[MB]()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadTraverse[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(MB) HKTRB,
|
||||||
|
_map func(HKTRB, func(MB) func(B) MB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
r MA, f func(A) HKTB) HKTRB {
|
||||||
|
return traverseWithIndex(_of, _map, _ap, r, F.Ignore1st[K](f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TraverseWithIndex[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(MB) HKTRB,
|
||||||
|
_map func(HKTRB, func(MB) func(B) MB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
f func(K, A) HKTB) func(MA) HKTRB {
|
||||||
|
|
||||||
|
return func(ma MA) HKTRB {
|
||||||
|
return traverseWithIndex(_of, _map, _ap, ma, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKTA = HKT<A>
|
||||||
|
// HKTB = HKT<B>
|
||||||
|
// HKTAB = HKT<func(A)B>
|
||||||
|
// HKTRB = HKT<MB>
|
||||||
|
func Traverse[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any](
|
||||||
|
_of func(MB) HKTRB,
|
||||||
|
_map func(HKTRB, func(MB) func(B) MB) HKTAB,
|
||||||
|
_ap func(HKTAB, HKTB) HKTRB,
|
||||||
|
|
||||||
|
f func(A) HKTB) func(MA) HKTRB {
|
||||||
|
|
||||||
|
return func(ma MA) HKTRB {
|
||||||
|
return MonadTraverse(_of, _map, _ap, ma, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKTA = HKT[A]
|
||||||
|
// HKTAA = HKT[func(A)MA]
|
||||||
|
// HKTRA = HKT[MA]
|
||||||
|
func Sequence[MA ~map[K]A, MKTA ~map[K]HKTA, K comparable, A, HKTA, HKTAA, HKTRA any](
|
||||||
|
_of func(MA) HKTRA,
|
||||||
|
_map func(HKTRA, func(MA) func(A) MA) HKTAA,
|
||||||
|
_ap func(HKTAA, HKTA) HKTRA,
|
||||||
|
|
||||||
|
ma MKTA) HKTRA {
|
||||||
|
return MonadTraverse(_of, _map, _ap, ma, F.Identity[HKTA])
|
||||||
|
}
|
28
internal/utils/utils.go
Normal file
28
internal/utils/utils.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Upper = strings.ToUpper
|
||||||
|
|
||||||
|
func Sum(left, right int) int {
|
||||||
|
return left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
func Double(value int) int {
|
||||||
|
return value * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func Triple(value int) int {
|
||||||
|
return value * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringLen(value string) int {
|
||||||
|
return len(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error() (int, error) {
|
||||||
|
return 0, errors.New("some error")
|
||||||
|
}
|
18
logging/logger.go
Normal file
18
logging/logger.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package Logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoggingCallbacks(loggers ...*log.Logger) (func(string, ...any), func(string, ...any)) {
|
||||||
|
switch len(loggers) {
|
||||||
|
case 0:
|
||||||
|
def := log.Default()
|
||||||
|
return def.Printf, def.Printf
|
||||||
|
case 1:
|
||||||
|
log0 := loggers[0]
|
||||||
|
return log0.Printf, log0.Printf
|
||||||
|
default:
|
||||||
|
return loggers[0].Printf, loggers[1].Printf
|
||||||
|
}
|
||||||
|
}
|
29
magma/array.go
Normal file
29
magma/array.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package magma
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
AR "github.com/ibm/fp-go/internal/array"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenericMonadConcatAll[GA ~[]A, A any](m Magma[A]) func(GA, A) A {
|
||||||
|
return func(as GA, first A) A {
|
||||||
|
return AR.Reduce(as, m.Concat, first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericConcatAll concats all items using the semigroup and a starting value
|
||||||
|
func GenericConcatAll[GA ~[]A, A any](m Magma[A]) func(A) func(GA) A {
|
||||||
|
ca := GenericMonadConcatAll[GA](m)
|
||||||
|
return func(a A) func(GA) A {
|
||||||
|
return F.Bind2nd(ca, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadConcatAll[A any](m Magma[A]) func([]A, A) A {
|
||||||
|
return GenericMonadConcatAll[[]A](m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConcatAll concats all items using the semigroup and a starting value
|
||||||
|
func ConcatAll[A any](m Magma[A]) func(A) func([]A) A {
|
||||||
|
return GenericConcatAll[[]A](m)
|
||||||
|
}
|
84
magma/magma.go
Normal file
84
magma/magma.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package magma
|
||||||
|
|
||||||
|
type Magma[A any] interface {
|
||||||
|
Concat(x A, y A) A
|
||||||
|
}
|
||||||
|
|
||||||
|
type magma[A any] struct {
|
||||||
|
c func(A, A) A
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self magma[A]) Concat(x A, y A) A {
|
||||||
|
return self.c(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeMagma[A any](c func(A, A) A) Magma[A] {
|
||||||
|
return magma[A]{c: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Reverse[A any](m Magma[A]) Magma[A] {
|
||||||
|
return MakeMagma(func(x A, y A) A {
|
||||||
|
return m.Concat(y, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterFirst[A any](p func(A) bool, c func(A, A) A, x A, y A) A {
|
||||||
|
if p(x) {
|
||||||
|
return c(x, y)
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterSecond[A any](p func(A) bool, c func(A, A) A, x A, y A) A {
|
||||||
|
if p(y) {
|
||||||
|
return c(x, y)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterFirst[A any](p func(A) bool) func(Magma[A]) Magma[A] {
|
||||||
|
return func(m Magma[A]) Magma[A] {
|
||||||
|
c := m.Concat
|
||||||
|
return MakeMagma(func(x A, y A) A {
|
||||||
|
return filterFirst(p, c, x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterSecond[A any](p func(A) bool) func(Magma[A]) Magma[A] {
|
||||||
|
return func(m Magma[A]) Magma[A] {
|
||||||
|
c := m.Concat
|
||||||
|
return MakeMagma(func(x A, y A) A {
|
||||||
|
return filterSecond(p, c, x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func first[A any](x A, y A) A {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func second[A any](x A, y A) A {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func First[A any]() Magma[A] {
|
||||||
|
return MakeMagma(first[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Second[A any]() Magma[A] {
|
||||||
|
return MakeMagma(second[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func endo[A any](f func(A) A, c func(A, A) A, x A, y A) A {
|
||||||
|
return c(f(x), f(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Endo[A any](f func(A) A) func(Magma[A]) Magma[A] {
|
||||||
|
return func(m Magma[A]) Magma[A] {
|
||||||
|
c := m.Concat
|
||||||
|
return MakeMagma(func(x A, y A) A {
|
||||||
|
return endo(f, c, x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
19
magma/magma_test.go
Normal file
19
magma/magma_test.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package magma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFirst(t *testing.T) {
|
||||||
|
m := First[string]()
|
||||||
|
|
||||||
|
assert.Equal(t, "a", m.Concat("a", "b"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecond(t *testing.T) {
|
||||||
|
m := Second[string]()
|
||||||
|
|
||||||
|
assert.Equal(t, "b", m.Concat("a", "b"))
|
||||||
|
}
|
25
main.go
Normal file
25
main.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isNonemptyString(val string) bool {
|
||||||
|
return val != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// var O = OptionModule{of: O_of, some: O_of, none: none, mp: OMap}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
opt_string := O.FromPredicate(isNonemptyString)
|
||||||
|
|
||||||
|
stringO1 := opt_string("Carsten")
|
||||||
|
stringO2 := opt_string("")
|
||||||
|
|
||||||
|
fmt.Println(stringO1)
|
||||||
|
fmt.Println(stringO2)
|
||||||
|
|
||||||
|
}
|
19
monoid/apply.go
Normal file
19
monoid/apply.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package monoid
|
||||||
|
|
||||||
|
import (
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApplicativeMonoid[A, HKTA, HKTFA any](
|
||||||
|
_of func(A) HKTA,
|
||||||
|
_map func(HKTA, func(A) func(A) A) HKTFA,
|
||||||
|
_ap func(HKTFA, HKTA) HKTA,
|
||||||
|
|
||||||
|
m Monoid[A],
|
||||||
|
) Monoid[HKTA] {
|
||||||
|
|
||||||
|
return MakeMonoid(
|
||||||
|
S.ApplySemigroup[A](_map, _ap, m).Concat,
|
||||||
|
_of(m.Empty()),
|
||||||
|
)
|
||||||
|
}
|
19
monoid/array.go
Normal file
19
monoid/array.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package monoid
|
||||||
|
|
||||||
|
import (
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenericConcatAll[GA ~[]A, A any](m Monoid[A]) func(GA) A {
|
||||||
|
return S.GenericConcatAll[GA](S.MakeSemigroup(m.Concat))(m.Empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConcatAll concatenates all values using the monoid and the default empty value
|
||||||
|
func ConcatAll[A any](m Monoid[A]) func([]A) A {
|
||||||
|
return GenericConcatAll[[]A](m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fold concatenates all values using the monoid and the default empty value
|
||||||
|
func Fold[A any](m Monoid[A]) func([]A) A {
|
||||||
|
return GenericConcatAll[[]A](m)
|
||||||
|
}
|
14
monoid/function.go
Normal file
14
monoid/function.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package monoid
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FunctionMonoid forms a monoid as long as you can provide a monoid for the codomain.
|
||||||
|
func FunctionMonoid[A, B any](M Monoid[B]) Monoid[func(A) B] {
|
||||||
|
return MakeMonoid(
|
||||||
|
S.FunctionSemigroup[A, B](M).Concat,
|
||||||
|
F.Constant1[A](M.Empty()),
|
||||||
|
)
|
||||||
|
}
|
33
monoid/monoid.go
Normal file
33
monoid/monoid.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package monoid
|
||||||
|
|
||||||
|
import (
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Monoid[A any] interface {
|
||||||
|
S.Semigroup[A]
|
||||||
|
Empty() A
|
||||||
|
}
|
||||||
|
|
||||||
|
type monoid[A any] struct {
|
||||||
|
c func(A, A) A
|
||||||
|
e A
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self monoid[A]) Concat(x A, y A) A {
|
||||||
|
return self.c(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self monoid[A]) Empty() A {
|
||||||
|
return self.e
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeMonoid creates a monoid given a concat function and an empty element
|
||||||
|
func MakeMonoid[A any](c func(A, A) A, e A) Monoid[A] {
|
||||||
|
return monoid[A]{c: c, e: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse returns the dual of a `Monoid`, obtained by swapping the arguments of `Concat`.
|
||||||
|
func Reverse[A any](m Monoid[A]) Monoid[A] {
|
||||||
|
return MakeMonoid(S.Reverse[A](m).Concat, m.Empty())
|
||||||
|
}
|
28
monoid/testing/rules.go
Normal file
28
monoid/testing/rules.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
AR "github.com/ibm/fp-go/internal/array"
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertLaws[A any](t *testing.T, m M.Monoid[A]) func(a A) bool {
|
||||||
|
e := m.Empty()
|
||||||
|
return func(a A) bool {
|
||||||
|
return assert.Equal(t, a, m.Concat(a, e), "Monoid right identity") &&
|
||||||
|
assert.Equal(t, a, m.Concat(e, a), "Monoid left identity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertLaws asserts the monoid laws for a dataset
|
||||||
|
func AssertLaws[A any](t *testing.T, m M.Monoid[A]) func(data []A) bool {
|
||||||
|
law := assertLaws(t, m)
|
||||||
|
|
||||||
|
return func(data []A) bool {
|
||||||
|
return AR.Reduce(data, func(result bool, value A) bool {
|
||||||
|
return result && law(value)
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
}
|
11
number/magma.go
Normal file
11
number/magma.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
import (
|
||||||
|
M "github.com/ibm/fp-go/magma"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MagmaSub[A int | int8 | int16 | int32 | int64 | float32 | float64 | complex64 | complex128]() M.Magma[A] {
|
||||||
|
return M.MakeMagma(func(first A, second A) A {
|
||||||
|
return first - second
|
||||||
|
})
|
||||||
|
}
|
18
number/magma_test.go
Normal file
18
number/magma_test.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
M "github.com/ibm/fp-go/magma"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSemigroupIsMagma(t *testing.T) {
|
||||||
|
sum := SemigroupSum[int]()
|
||||||
|
|
||||||
|
var magma M.Magma[int] = sum
|
||||||
|
|
||||||
|
assert.Equal(t, sum.Concat(1, 2), magma.Concat(1, 2))
|
||||||
|
assert.Equal(t, sum.Concat(1, 2), sum.Concat(2, 1))
|
||||||
|
}
|
13
number/monoid.go
Normal file
13
number/monoid.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
import (
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MonoidSum[A int | int8 | int16 | int32 | int64 | float32 | float64 | complex64 | complex128]() M.Monoid[A] {
|
||||||
|
s := SemigroupSum[A]()
|
||||||
|
return M.MakeMonoid(
|
||||||
|
s.Concat,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
11
number/monoid_test.go
Normal file
11
number/monoid_test.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
M "github.com/ibm/fp-go/monoid/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMonoidSum(t *testing.T) {
|
||||||
|
M.AssertLaws(t, MonoidSum[int]())([]int{0, 1, 1000, -1})
|
||||||
|
}
|
11
number/semigroup.go
Normal file
11
number/semigroup.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
import (
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SemigroupSum[A int | int8 | int16 | int32 | int64 | float32 | float64 | complex64 | complex128]() S.Semigroup[A] {
|
||||||
|
return S.MakeSemigroup(func(first A, second A) A {
|
||||||
|
return first + second
|
||||||
|
})
|
||||||
|
}
|
14
option/apply.go
Normal file
14
option/apply.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Option[A]] {
|
||||||
|
return S.ApplySemigroup(MonadMap[A, func(A) A], MonadAp[A, A], s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Option[A]] {
|
||||||
|
return M.ApplicativeMonoid(Of[A], MonadMap[A, func(A) A], MonadAp[A, A], m)
|
||||||
|
}
|
31
option/array.go
Normal file
31
option/array.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
RA "github.com/ibm/fp-go/internal/array"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraverseArray transforms an array
|
||||||
|
func TraverseArrayG[GA ~[]A, GB ~[]B, A, B any](f func(A) Option[B]) func(GA) Option[GB] {
|
||||||
|
return RA.Traverse[GA](
|
||||||
|
Of[GB],
|
||||||
|
MonadMap[GB, func(B) GB],
|
||||||
|
MonadAp[B, GB],
|
||||||
|
|
||||||
|
f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraverseArray transforms an array
|
||||||
|
func TraverseArray[A, B any](f func(A) Option[B]) func([]A) Option[[]B] {
|
||||||
|
return TraverseArrayG[[]A, []B](f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SequenceArrayG[GA ~[]A, GOA ~[]Option[A], A any](ma GOA) Option[GA] {
|
||||||
|
return TraverseArrayG[GOA, GA](F.Identity[Option[A]])(ma)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SequenceArray converts a homogeneous sequence of either into an either of sequence
|
||||||
|
func SequenceArray[A any](ma []Option[A]) Option[[]A] {
|
||||||
|
return SequenceArrayG[[]A](ma)
|
||||||
|
}
|
21
option/array_test.go
Normal file
21
option/array_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSequenceArray(t *testing.T) {
|
||||||
|
|
||||||
|
one := Of(1)
|
||||||
|
two := Of(2)
|
||||||
|
|
||||||
|
res := F.Pipe1(
|
||||||
|
[]Option[int]{one, two},
|
||||||
|
SequenceArray[int],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, res, Of([]int{1, 2}))
|
||||||
|
}
|
22
option/eq.go
Normal file
22
option/eq.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
EQ "github.com/ibm/fp-go/eq"
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constructs an equal predicate for an `Option`
|
||||||
|
func Eq[A any](a EQ.Eq[A]) EQ.Eq[Option[A]] {
|
||||||
|
// some convenient shortcuts
|
||||||
|
fld := Fold(
|
||||||
|
F.Constant(Fold(F.ConstTrue, F.Constant1[A](false))),
|
||||||
|
F.Flow2(F.Curry2(a.Equals), F.Bind1st(Fold[A, bool], F.ConstFalse)),
|
||||||
|
)
|
||||||
|
// convert to an equals predicate
|
||||||
|
return EQ.FromEquals(F.Uncurry2(fld))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStrictEquals constructs an `Eq` from the canonical comparison function
|
||||||
|
func FromStrictEquals[A comparable]() EQ.Eq[Option[A]] {
|
||||||
|
return Eq(EQ.FromStrictEquals[A]())
|
||||||
|
}
|
26
option/eq_test.go
Normal file
26
option/eq_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEq(t *testing.T) {
|
||||||
|
|
||||||
|
r1 := Of(1)
|
||||||
|
r2 := Of(1)
|
||||||
|
r3 := Of(2)
|
||||||
|
|
||||||
|
n1 := None[int]()
|
||||||
|
|
||||||
|
eq := FromStrictEquals[int]()
|
||||||
|
|
||||||
|
assert.True(t, eq.Equals(r1, r1))
|
||||||
|
assert.True(t, eq.Equals(r1, r2))
|
||||||
|
assert.False(t, eq.Equals(r1, r3))
|
||||||
|
assert.False(t, eq.Equals(r1, n1))
|
||||||
|
|
||||||
|
assert.True(t, eq.Equals(n1, n1))
|
||||||
|
assert.False(t, eq.Equals(n1, r2))
|
||||||
|
}
|
58
option/legacy.go
Normal file
58
option/legacy.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type OptionTag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoneTag OptionTag = iota
|
||||||
|
SomeTag
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option defines a data structure that logically holds a value or not
|
||||||
|
type Option[A any] struct {
|
||||||
|
Tag OptionTag
|
||||||
|
Value A
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints some debug info for the object
|
||||||
|
func (s Option[A]) String() string {
|
||||||
|
switch s.Tag {
|
||||||
|
case NoneTag:
|
||||||
|
return fmt.Sprintf("None[%T]", s.Value)
|
||||||
|
case SomeTag:
|
||||||
|
return fmt.Sprintf("Some[%T](%v)", s.Value, s.Value)
|
||||||
|
}
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNone[T any](val Option[T]) bool {
|
||||||
|
return val.Tag == NoneTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func Some[T any](value T) Option[T] {
|
||||||
|
return Option[T]{Tag: SomeTag, Value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Of[T any](value T) Option[T] {
|
||||||
|
return Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func None[T any]() Option[T] {
|
||||||
|
return Option[T]{Tag: NoneTag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSome[T any](val Option[T]) bool {
|
||||||
|
return val.Tag == SomeTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadFold[A, B any](ma Option[A], onNone func() B, onSome func(A) B) B {
|
||||||
|
if IsNone(ma) {
|
||||||
|
return onNone()
|
||||||
|
}
|
||||||
|
return onSome(ma.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unwrap[A any](ma Option[A]) (A, bool) {
|
||||||
|
return ma.Value, ma.Tag == SomeTag
|
||||||
|
}
|
33
option/logger.go
Normal file
33
option/logger.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
L "github.com/ibm/fp-go/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func _log[A any](left func(string, ...any), right func(string, ...any), prefix string) func(Option[A]) Option[A] {
|
||||||
|
return Fold(
|
||||||
|
func() Option[A] {
|
||||||
|
left("%s", prefix)
|
||||||
|
return None[A]()
|
||||||
|
},
|
||||||
|
func(a A) Option[A] {
|
||||||
|
right("%s: %v", prefix, a)
|
||||||
|
return Some(a)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Logger[A any](loggers ...*log.Logger) func(string) func(Option[A]) Option[A] {
|
||||||
|
left, right := L.LoggingCallbacks(loggers...)
|
||||||
|
return func(prefix string) func(Option[A]) Option[A] {
|
||||||
|
delegate := _log[A](left, right, prefix)
|
||||||
|
return func(ma Option[A]) Option[A] {
|
||||||
|
return F.Pipe1(
|
||||||
|
delegate(ma),
|
||||||
|
ChainTo[A](ma),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
option/modern._go
Normal file
61
option/modern._go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//go:build disabled
|
||||||
|
|
||||||
|
package option
|
||||||
|
|
||||||
|
// Option will be of type T or None
|
||||||
|
type Option[T any] interface {
|
||||||
|
fmt.Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
type none struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var const_none = none{}
|
||||||
|
|
||||||
|
func (none) String() string {
|
||||||
|
return "None"
|
||||||
|
}
|
||||||
|
|
||||||
|
type some[T any] struct {
|
||||||
|
v T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s some[T]) String() string {
|
||||||
|
return fmt.Sprintf("Some[%v]", s.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNone[T any](val Option[T]) bool {
|
||||||
|
return val == const_none
|
||||||
|
}
|
||||||
|
|
||||||
|
func Some[T any](value T) Option[T] {
|
||||||
|
return some[T]{v: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Of[T any](value T) Option[T] {
|
||||||
|
return Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func None[T any]() Option[T] {
|
||||||
|
return const_none
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSome[T any](val Option[T]) bool {
|
||||||
|
return !IsNone(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadFold[A, B any](ma Option[A], onNone func() B, onSome func(A) B) B {
|
||||||
|
if IsNone(ma) {
|
||||||
|
return onNone()
|
||||||
|
}
|
||||||
|
return onSome(ma.(some[A]).v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unwrap[A any](a A) func(Option[A]) (A, bool) {
|
||||||
|
return func(ma Option[A]) (A, bool) {
|
||||||
|
if IsNone(ma) {
|
||||||
|
return a, false
|
||||||
|
}
|
||||||
|
return ma.(some[A]).v, true
|
||||||
|
}
|
||||||
|
}
|
38
option/monoid.go
Normal file
38
option/monoid.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
M "github.com/ibm/fp-go/monoid"
|
||||||
|
S "github.com/ibm/fp-go/semigroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Semigroup[A any]() func(S.Semigroup[A]) S.Semigroup[Option[A]] {
|
||||||
|
return func(s S.Semigroup[A]) S.Semigroup[Option[A]] {
|
||||||
|
concat := s.Concat
|
||||||
|
return S.MakeSemigroup(
|
||||||
|
func(x, y Option[A]) Option[A] {
|
||||||
|
return MonadFold(x, F.Constant(y), func(left A) Option[A] {
|
||||||
|
return MonadFold(y, F.Constant(x), func(right A) Option[A] {
|
||||||
|
return Some(concat(left, right))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monoid returning the left-most non-`None` value. If both operands are `Some`s then the inner values are
|
||||||
|
// concatenated using the provided `Semigroup`
|
||||||
|
//
|
||||||
|
// | x | y | concat(x, y) |
|
||||||
|
// | ------- | ------- | ------------------ |
|
||||||
|
// | none | none | none |
|
||||||
|
// | some(a) | none | some(a) |
|
||||||
|
// | none | some(b) | some(b) |
|
||||||
|
// | some(a) | some(b) | some(concat(a, b)) |
|
||||||
|
func Monoid[A any]() func(S.Semigroup[A]) M.Monoid[Option[A]] {
|
||||||
|
sg := Semigroup[A]()
|
||||||
|
return func(s S.Semigroup[A]) M.Monoid[Option[A]] {
|
||||||
|
return M.MakeMonoid(sg(s).Concat, None[A]())
|
||||||
|
}
|
||||||
|
}
|
20
option/number/number.go
Normal file
20
option/number/number.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
O "github.com/ibm/fp-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func atoi(value string) (int, bool) {
|
||||||
|
data, err := strconv.Atoi(value)
|
||||||
|
return data, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Atoi converts a string to an integer
|
||||||
|
Atoi = O.Optionize1(atoi)
|
||||||
|
// Itoa converts an integer to a string
|
||||||
|
Itoa = F.Flow2(strconv.Itoa, O.Of[string])
|
||||||
|
)
|
205
option/option.go
Normal file
205
option/option.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// package option implements the Option monad, a data type that can have a defined value or none
|
||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/ibm/fp-go/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fromPredicate[A any](a A, pred func(A) bool) Option[A] {
|
||||||
|
if pred(a) {
|
||||||
|
return Some(a)
|
||||||
|
}
|
||||||
|
return None[A]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromPredicate[A any](pred func(A) bool) func(A) Option[A] {
|
||||||
|
return F.Bind2nd(fromPredicate[A], pred)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromNillable[A any](a *A) Option[*A] {
|
||||||
|
return fromPredicate(a, F.IsNonNil[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromValidation[A, B any](a A, f func(A) (B, bool)) Option[B] {
|
||||||
|
b, ok := f(a)
|
||||||
|
if ok {
|
||||||
|
return Some(b)
|
||||||
|
}
|
||||||
|
return None[B]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromValidation0[A any](f func() (A, bool)) Option[A] {
|
||||||
|
a, ok := f()
|
||||||
|
if ok {
|
||||||
|
return Some(a)
|
||||||
|
}
|
||||||
|
return None[A]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromValidation[A, B any](f func(A) (B, bool)) func(A) Option[B] {
|
||||||
|
return F.Bind2nd(fromValidation[A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonadAp is the applicative functor of Option
|
||||||
|
func MonadAp[A, B any](fab Option[func(A) B], fa Option[A]) Option[B] {
|
||||||
|
return MonadFold(fab, None[B], func(ab func(A) B) Option[B] {
|
||||||
|
return MonadFold(fa, None[B], F.Flow2(ab, Some[B]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ap[A, B any](fa Option[A]) func(Option[func(A) B]) Option[B] {
|
||||||
|
return F.Bind2nd(MonadAp[A, B], fa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMap[A, B any](fa Option[A], f func(A) B) Option[B] {
|
||||||
|
return MonadChain(fa, F.Flow2(f, Some[B]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map[A, B any](f func(a A) B) func(Option[A]) Option[B] {
|
||||||
|
return Chain(F.Flow2(f, Some[B]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadMapTo[A, B any](fa Option[A], b B) Option[B] {
|
||||||
|
return MonadMap(fa, F.Constant1[A](b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapTo[A, B any](b B) func(Option[A]) Option[B] {
|
||||||
|
return F.Bind2nd(MonadMapTo[A, B], b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryCatch[A any](f func() (A, error)) Option[A] {
|
||||||
|
val, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return None[A]()
|
||||||
|
}
|
||||||
|
return Some(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fold[A, B any](onNone func() B, onSome func(a A) B) func(ma Option[A]) B {
|
||||||
|
return func(ma Option[A]) B {
|
||||||
|
return MonadFold(ma, onNone, onSome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOrElse[A any](onNone func() A) func(Option[A]) A {
|
||||||
|
return Fold(onNone, F.Identity[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChain[A, B any](fa Option[A], f func(A) Option[B]) Option[B] {
|
||||||
|
return MonadFold(fa, None[B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Chain[A, B any](f func(A) Option[B]) func(Option[A]) Option[B] {
|
||||||
|
return F.Bind2nd(MonadChain[A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChainTo[A, B any](ma Option[A], mb Option[B]) Option[B] {
|
||||||
|
return mb
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChainTo[A, B any](mb Option[B]) func(Option[A]) Option[B] {
|
||||||
|
return F.Bind2nd(MonadChainTo[A, B], mb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadChainFirst[A, B any](ma Option[A], f func(A) Option[B]) Option[A] {
|
||||||
|
return MonadChain(ma, func(a A) Option[A] {
|
||||||
|
return MonadMap(f(a), F.Constant1[B](a))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChainFirst[A, B any](f func(A) Option[B]) func(Option[A]) Option[A] {
|
||||||
|
return F.Bind2nd(MonadChainFirst[A, B], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flatten[A any](mma Option[Option[A]]) Option[A] {
|
||||||
|
return MonadChain(mma, F.Identity[Option[A]])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Alt[A any](that func() Option[A]) func(Option[A]) Option[A] {
|
||||||
|
return Fold(that, Of[A])
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadSequence2[T1, T2, R any](o1 Option[T1], o2 Option[T2], f func(T1, T2) Option[R]) Option[R] {
|
||||||
|
return MonadFold(o1, None[R], func(t1 T1) Option[R] {
|
||||||
|
return MonadFold(o2, None[R], func(t2 T2) Option[R] {
|
||||||
|
return f(t1, t2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sequence2[T1, T2, R any](f func(T1, T2) Option[R]) func(Option[T1], Option[T2]) Option[R] {
|
||||||
|
return func(o1 Option[T1], o2 Option[T2]) Option[R] {
|
||||||
|
return MonadSequence2(o1, o2, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Reduce[A, B any](f func(B, A) B, initial B) func(Option[A]) B {
|
||||||
|
return Fold(F.Constant(initial), F.Bind1st(f, initial))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter converts an optional onto itself if it is some and the predicate is true
|
||||||
|
func Filter[A any](pred func(A) bool) func(Option[A]) Option[A] {
|
||||||
|
return Fold(None[A], F.Ternary(pred, Of[A], F.Ignore1[A](None[A])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Optionize0[R any](f func() (R, bool)) func() Option[R] {
|
||||||
|
return func() Option[R] {
|
||||||
|
return fromValidation0(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Optionize1[T1, R any](f func(T1) (R, bool)) func(T1) Option[R] {
|
||||||
|
return func(t1 T1) Option[R] {
|
||||||
|
return fromValidation0(func() (R, bool) {
|
||||||
|
return f(t1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unoptionize1[T1, R any](f func(T1) Option[R]) func(T1) (R, bool) {
|
||||||
|
return func(t1 T1) (R, bool) {
|
||||||
|
return Unwrap(f(t1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Optionize2[T1, T2, R any](f func(t1 T1, t2 T2) (R, bool)) func(t1 T1, t2 T2) Option[R] {
|
||||||
|
return func(t1 T1, t2 T2) Option[R] {
|
||||||
|
return fromValidation0(func() (R, bool) {
|
||||||
|
return f(t1, t2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unoptionize2[T1, T2, R any](f func(T1, T2) Option[R]) func(T1, T2) (R, bool) {
|
||||||
|
return func(t1 T1, t2 T2) (R, bool) {
|
||||||
|
return Unwrap(f(t1, t2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Optionize3[T1, T2, T3, R any](f func(t1 T1, t2 T2, t3 T3) (R, bool)) func(t1 T1, t2 T2, t3 T3) Option[R] {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3) Option[R] {
|
||||||
|
return fromValidation0(func() (R, bool) {
|
||||||
|
return f(t1, t2, t3)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unoptionize3[T1, T2, T3, R any](f func(T1, T2, T3) Option[R]) func(T1, T2, T3) (R, bool) {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3) (R, bool) {
|
||||||
|
return Unwrap(f(t1, t2, t3))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Optionize4[T1, T2, T3, T4, R any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (R, bool)) func(t1 T1, t2 T2, t3 T3, t4 T4) Option[R] {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4) Option[R] {
|
||||||
|
return fromValidation0(func() (R, bool) {
|
||||||
|
return f(t1, t2, t3, t4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unoptionize4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) Option[R]) func(T1, T2, T3, T4) (R, bool) {
|
||||||
|
return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, bool) {
|
||||||
|
return Unwrap(f(t1, t2, t3, t4))
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user