1
0
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:
Dr. Carsten Leue
2023-07-07 22:31:06 +02:00
parent 71c47ca560
commit c07df5c771
128 changed files with 5827 additions and 2 deletions

126
README.md
View File

@ -1,2 +1,124 @@
# fp-go # Functional programming library for golang
functional programming library for golang
![logo](resources/images/logo.png)
## 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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}))
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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,
)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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]
)

BIN
fp-go.exe Normal file

Binary file not shown.

52
function/bind.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
package function
func ToAny[A any](a A) any {
return any(a)
}

61
function/variadic.go Normal file
View 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
View 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
View 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=

View 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
View 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)
}
}

View 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
View 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)
}

View 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
View 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)
}
}

View 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
View 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
}

View 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)
}
}

View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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